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



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

问题在于,给定 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答案将变得很有用。

最新更新