这是我之前关于类型索引的地图question的后续内容。在评论中讨论之后,我在这里发布实际问题,看看是否有一种利用依赖类型编程来解决这个问题的简洁方法。
问题在于,给定了类型类函数所需的运行时信息 - 我们有不同类型的消息通过线路传输,我们使用该函数使用运行时配置进行消息特定处理 - 如何做我们将运行时信息(每个类型实例的一个运行时配置)传递给该函数?
下面的玩具代码带有注释 - 我们在/* find the range of (the first occurence of) a given
attribute 'attrName' for a given value 'forValue'. */
extension NSAttributedString {
func findRangeOfAttribute(attrName: String, forValue value: AnyObject) -> NSRange? {
var rng = NSRange()
/* Is attribute (with given value) in range 0...X ? */
if let val = self.attribute(attrName, atIndex: 0, effectiveRange: &rng) where val.isEqual(value) { return rng }
/* If not, is attribute (with given value) anywhere in range X+1..<end? */
else if
let from = rng.toRange()?.endIndex where from < self.length - 1,
let val = self.attribute(attrName, atIndex: from, effectiveRange: &rng) where val.isEqual(value) { return rng }
/* if none of the above, return nil */
return nil
}
}
中使用类型类函数/* Example */
let fooString = "foo foo foo foo foo foo foo"
var fooAttrString = NSMutableAttributedString(string: fooString)
let selectedRange: NSRange = NSMakeRange(12,6)
let myRange = NSRange(location: 12, length: 6)
let attr = [ NSStrikethroughStyleAttributeName: 2 ]
fooAttrString.addAttributes(attr, range: myRange)
/* Example usage: extension */
if let rngOfFirstStrikethrough = fooAttrString.findRangeOfAttribute(NSStrikethroughStyleAttributeName, forValue: 2) {
print(rngOfStrikethrough) // (12,6)
}
获取运行时信息并将其应用于f
- 顺便说一下,消息类型集g
是固定的 - 所以,如果需要,我们可以使用封闭式家庭:
f
到目前为止,我的解决方案是a
中的类型索引地图,它会查找module Main where
main :: IO ()
main = do
let runtimeinfo = Mesg { aString = "someheader"}
builder = (\x -> Mesg { aString = (aString runtimeinfo) ++ (aString x)})
-- should call function "app" on this line which will call function "g"
return ()
data Mesg = Mesg {aString :: String} deriving Show
class Process a where
f :: a -> a -- FYI, in actual app, output type is (StateT a IO Builder)
-- We can't define builder below at compile-time because it is created at run-time in main above
--builder :: a -> a
instance Process Mesg where
f inp = Mesg { aString = (reverse (aString inp))} -- contrived example - a placeholder for some processing on message
-- g is not directly reachable from main - main calls a function "app" which
-- calls g (after receiving "inp" of type "a" over the wire) - so, to pass
-- builder, we have to pass it along. builder is from runtime configuration -
-- in this example, it is created in main. If it were part of typeclass Process,
-- we won't need to pass it along
g :: Process a => (a -> a) -> a -> a
g builder inp = builder $ f inp -- we call processing function f here with runtime builder
-- Alternative approach pseudo code - map here is created in main, and passed to g via app
{--
g :: (Process a, Typeable a) => Map String Dynamic -> a -> Maybe a
g map inp = (retrieve corresponding builder from map using type-indexed string), apply here with f
--}
类型g
。
答案 0 :(得分:3)
这个问题确实不是自包含的,但听起来您可以使用reflection
包中的设施。特别是,它允许您在类型类实例中使用运行时信息。例如,你可以写这样的东西(未经测试)。
{-# LANGUAGE ScopedTypeVariables,
UndecidableInstances, .... #-}
import Data.Proxy
import Data.Reflection
data Configuration a = Configuration
{ the_builder :: a -> a
, someOtherThing :: Int
, whatever :: Char }
class Buildable a where
builder :: a -> a
newtype Yeah s a = Yeah a
instance Reifies s (Configuration a) =>
Buildable (Yeah s a) where
builder (Yeah x) = Yeah $ the_builder (reflect (Proxy :: Proxy s)) x
然后你可以写
reify config $ \(_ :: Proxy s) -> expr
并且在expr
内,Yeah s a
类型将是Buildable
类的实例。
答案 1 :(得分:1)
如果您能够更改builder
的声明,为什么不以老式方式进行,只使用monad进行配置?
data Config = Config {
theHeader :: String,
somethingElse :: Int,
andAnotherThing :: Bool
}
class Buildable a where
build :: MonadReader Config m => String -> m a
data Msg = Msg { msg :: String } deriving (Show)
instance Buildable Msg where
build body = do
config <- ask
return $ Msg { msg = theHeader config ++ body }
-- imagine we're getting this from a file in the IO monad
readConfigFile = return $ Config {
theHeader = "foo",
somethingElse = 4,
andAnotherThing = False
}
-- imagine we're reading this from the wire
getFromSocket = return "bar"
main = do
config <- readConfigFile
body <- getFromSocket
msg <- runReaderT (build body) config
print msg
如果您不拥有该课程并且无法将单一上下文添加到reflection
类型,则 @ dfeuer的build
答案会变得有用。