传递多态函数的运行时信息

时间:2016-04-03 01:01:31

标签: haskell typeclass

这是我之前关于类型索引的地图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

2 个答案:

答案 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答案会变得有用。