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

Haskell 传递多态函数的运行时信息,haskell,typeclass,Haskell,Typeclass,这是我之前关于类型索引映射的后续内容。在评论中的讨论之后,我将实际问题发布在这里,看看是否有一种简洁的方法可以使用依赖类型编程来解决这个问题 问题是,给定typeclass函数所需的运行时信息-我们有不同类型的消息通过网络传输,我们使用该函数使用运行时配置执行特定于消息的处理-我们如何传递运行时信息(每个类型实例一个运行时配置)这个功能 下面是带注释的玩具代码-我们在g中使用typeclass函数f,该函数获取运行时信息并将其应用于f-顺便说一句,消息类型集a是固定的-因此,如果需要,我们可以使

这是我之前关于类型索引映射的后续内容。在评论中的讨论之后,我将实际问题发布在这里,看看是否有一种简洁的方法可以使用依赖类型编程来解决这个问题

问题是,给定typeclass函数所需的运行时信息-我们有不同类型的消息通过网络传输,我们使用该函数使用运行时配置执行特定于消息的处理-我们如何传递运行时信息(每个类型实例一个运行时配置)这个功能

下面是带注释的玩具代码-我们在
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