Bru*_*der 13 haskell monad-transformers
我想提出一个模块化的程序设计,我再次请求你的帮助.
作为以下帖子的后续Monad变形金刚与Haskell中的传递参数和大规模设计,我正在尝试构建两个独立模块,使用Monad变换器但暴露Monad不可知函数,然后结合Monad不可知函数来自每个这些模块成为一个新的Monad不可知功能.
我已经无法运行相结合的功能,例如我怎么打电话mainProgram使用runReaderT下面的例子?
附属问题是:是否有更好的方法来实现相同的模块化设计目标?
该示例有两个模拟模块(但编译),一个执行日志记录,另一个读取用户输入并对其进行操作.组合功能读取用户输入,记录并打印它.
{-# LANGUAGE FlexibleContexts #-}
module Stackoverflow2 where
import Control.Monad.Reader
----
---- From Log Module - Writes the passed message in the log
----
data LogConfig = LC { logFile :: FilePath }
doLog :: (MonadIO m, MonadReader LogConfig m) => String -> m ()
doLog _ = undefined
----
---- From UserProcessing Module - Reads the user Input and changes it to the configured case
----
data MessageCase = LowerCase | UpperCase deriving (Show, Read)
getUserInput :: (MonadReader MessageCase m, MonadIO m) => m String
getUserInput = undefined
----
---- Main program that combines the two
----
mainProgram :: (MonadReader MessageCase m, MonadReader LogConfig m, MonadIO m) => m ()
mainProgram = do input <- getUserInput
doLog input
liftIO $ putStrLn $ "Entry logged: " ++ input
Run Code Online (Sandbox Code Playgroud)
dfl*_*str 20
有一种方法可以编写完全模块化的程序版本.解决问题的方法是将读者配置捆绑到一个数据结构中,然后定义描述特定功能对该数据结构所需的部分接口的类型类.例如:
class LogConfiguration c where
logFile :: c -> FilePath
doLog :: (MonadIO m, LogConfiguration c, MonadReader c m) => String -> m ()
doLog = do
file <- asks logFile
-- ...
class MessageCaseConfiguration c where
isLowerCase :: c -> Bool
getUserInput :: (MonadIO m, MessageCaseConfiguration c, MonadReader c m) => m String
getUserInput = do
lc <- asks isLowerCase
-- ...
data LogConfig = LC { logConfigFile :: FilePath }
data MessageCase = LowerCase | UpperCase
data Configuration = Configuration { logging :: LogConfig, casing :: MessageCase }
instance LogConfiguration Configuration where
logFile = logConfigFile . logging
instance MessageCaseConfiguration Configuration where
isLowerCase c = case casing c of
LowerCase -> True
UpperCase -> False
mainProgram :: (MonadIO m, MessageCaseConfiguration c, LogConfiguration c, MonadReader c m) => m ()
mainProgram = do
input <- getUserInput
doLog input
liftIO . putStrLn $ "Entry logged: " ++ input
Run Code Online (Sandbox Code Playgroud)
现在,你可以调用mainProgram一个Configuration在一个ReaderT单子,它会按照您期望的那样.
sha*_*ang 10
您的mainProgram签名是有问题的,因为MonadReader类型类包含功能依赖性MonadReader r m | m -> r.这实质上意味着单个具体类型不能具有MonadReader多个不同类型的实例.因此,当您说该类型m具有两个实例MonadReader MessageCase并且MonadReader LogConfig它违反了依赖性声明时.
最简单的解决方案是更改mainProgram为具有非泛型类型:
mainProgram :: ReaderT MessageCase (ReaderT LogConfig IO) ()
mainProgram = do input <- getUserInput
lift $ doLog input
liftIO $ putStrLn $ "Entry logged: " ++ input
Run Code Online (Sandbox Code Playgroud)
这也需要明确lift的doLog.
现在你可以单独mainProgram运行每个运行ReaderT,如下所示:
main :: IO ()
main = do
let messageCase = undefined :: MessageCase
logConfig = undefined :: LogConfig
runReaderT (runReaderT mainProgram messageCase) logConfig
Run Code Online (Sandbox Code Playgroud)
如果你想拥有一个使用两个不同MonadReader实例的泛型函数,你需要在签名中明确指出一个阅读器是另一个阅读器顶部的monad变换器.
mainProgram :: (MonadTrans mt, MonadReader MessageCase (mt m), MonadReader LogConfig m, MonadIO (mt m), MonadIO m) => mt m ()
mainProgram = do input <- getUserInput
lift $ doLog input
liftIO $ putStrLn $ "Entry logged: " ++ input
Run Code Online (Sandbox Code Playgroud)
然而,这具有令人遗憾的效果,即该函数不再是完全通用的,因为两个读取器出现在monad堆栈中的顺序被锁定.也许有一种更清洁的方法来实现这一点,但是我没有能够在不牺牲(甚至更多)通用性的情况下从头脑中找到一个.