Haskell 传递多态函数的运行时信息
这是我之前关于类型索引映射的后续内容。在评论中的讨论之后,我将实际问题发布在这里,看看是否有一种简洁的方法可以使用依赖类型编程来解决这个问题 问题是,给定typeclass函数所需的运行时信息-我们有不同类型的消息通过网络传输,我们使用该函数使用运行时配置执行特定于消息的处理-我们如何传递运行时信息(每个类型实例一个运行时配置)这个功能 下面是带注释的玩具代码-我们在Haskell 传递多态函数的运行时信息,haskell,typeclass,Haskell,Typeclass,这是我之前关于类型索引映射的后续内容。在评论中的讨论之后,我将实际问题发布在这里,看看是否有一种简洁的方法可以使用依赖类型编程来解决这个问题 问题是,给定typeclass函数所需的运行时信息-我们有不同类型的消息通过网络传输,我们使用该函数使用运行时配置执行特定于消息的处理-我们如何传递运行时信息(每个类型实例一个运行时配置)这个功能 下面是带注释的玩具代码-我们在g中使用typeclass函数f,该函数获取运行时信息并将其应用于f-顺便说一句,消息类型集a是固定的-因此,如果需要,我们可以使
g
中使用typeclass函数f
,该函数获取运行时信息并将其应用于f
-顺便说一句,消息类型集a
是固定的-因此,如果需要,我们可以使用封闭的typefamilies:
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
的类型索引映射 这个问题其实并不完整,但听起来您可能可以使用软件包中的设施。特别是,它允许您在typeclass实例中使用运行时信息。例如,您可以编写如下内容(未经测试)
然后你就可以写了
reify config $ \(_ :: Proxy s) -> expr
在
expr
中,类型Yeah s a
将是可构建的类的一个实例。这个问题确实不是独立的,但听起来您可能可以使用包中的工具。特别是,它允许您在typeclass实例中使用运行时信息。例如,您可以编写如下内容(未经测试)
然后你就可以写了
reify config $ \(_ :: Proxy s) -> expr
在expr
中,类型yesa
将是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
data Config=Config{
标题::字符串,
一些东西:Int,
还有其他的:布尔
}
类可构建的一个where
构建::monawarder配置m=>String->ma
数据Msg=Msg{Msg::String}派生(显示)
实例可构建消息,其中
构建主体=执行
配置如果您能够更改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
data Config=Config{
标题::字符串,
一些东西:Int,
还有其他的:布尔
}
类可构建的一个where
构建::monawarder配置m=>String->ma
数据Msg=Msg{Msg::String}派生(显示)
实例可构建消息,其中
构建主体=执行
我认为g
的类型很奇怪。。它为什么返回字符串?为什么不从一个函数开始呢?:(进程a,可键入的a)=>映射字符串动态->可能(a->a)
,它从TypeRep
计算字符串并返回所需的函数(当然可能是包装的)。@user2407038,我对g
的注释版本不满意。应该返回可能是一个。修正了。g
的类型很奇怪。。它为什么返回字符串?为什么不从一个函数开始呢?:(进程a,可键入的a)=>映射字符串动态->可能(a->a)
,它从TypeRep
计算字符串并返回所需的函数(当然可能是包装的)。@user2407038,我对g
的注释版本不满意。应该返回可能是一个。修正了。如果我们在main中调用reify,那么创建的实例是否在主体外部可见,即它具有类似编译时typeclass实例的全局作用域?这是关于反射动态类型类实例的一件我还不清楚的事情。@Sal实例Buildable(是的)
是一个普通实例;它与任何实例一样具有全局范围。如果没有具体化s(配置a)
上下文,您就无法满足它,这意味着该实例只能在调用具体化
的过程中使用。在实践中,所有这一切意味着你不能编写第二个实例Buildable(Yeah s a)
,它与这个实例重叠,因为GHC从不使用实例上下文在两个实例之间进行选择。如果我们在main中调用reify,那么创建的实例是否在主体之外可见,即它具有类似编译时typeclass实例的全局作用域?这是关于反射动态类型类实例的一件我还不清楚的事情。@Sal实例Buildable(是的)
是一个普通实例;它与任何实例一样具有全局范围。如果没有具体化s(配置a)
上下文,您就无法满足它,这意味着该实例只能在调用具体化
的过程中使用。在实践中,所有这一切意味着你不能编写第二个可构建的实例(是的是一个)
,它与这个实例重叠,因为GHC从不使用实例上下文在两个实例之间进行选择。是的,我不拥有构建
你不拥有你所问的过程
类?是的,我确实拥有进程
类。那么有什么问题呢?将build
放入您的Process
类中,就像您最初想要的那样,使用一个允许它访问运行时配置的类型。保持简单。您不需要反射
或数据种类
或动态
。是的,您需要弄清楚如何重构流程类。那么就不需要反思了。是的,我没有拥有构建
你没有拥有你问题中的流程
类?是的,我拥有流程
类。那么问题出在哪里?将build
放入您的Process
类中,就像您最初想要的那样,使用一个允许它访问运行时配置的类型。保持简单。你不需要反射
或数据种类
或动态
。是的,你需要弄清楚如何重构pro