这是我之前关于类型索引映射的问题的后续。在评论中的讨论之后,我在这里发布实际问题,看看是否有一种巧妙的方法可以使用依赖类型编程来解决这个问题。
问题在于,给定 typeclass 函数所需的运行时信息 - 我们有不同类型的消息通过网络发送,并且我们使用该函数使用运行时配置进行特定于消息的处理 - 我们如何将运行时信息(每个类型实例一个运行时配置(传递给该函数?
下面带有注释的玩具代码 - 我们在 g
中使用 typeclass 函数 f
获取运行时信息并将其应用于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
中的类型索引映射,它查找builder
类型a
。
这个问题真的不是独立的,但听起来你可以使用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
类的实例。
如果您能够更改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
如果您不拥有该类并且无法将 monadic 上下文添加到 build
的类型中,@dfeuer的reflection
答案将变得很有用。