模块化程序设计 - 在Monad不可知论功能中结合Monad变换器 [英] Modular Program Design - Combining Monad Transformers in Monad Agnostic functions
问题描述
我试图想出一个模块化的程序设计,我再次请求您的帮助。
作为以下文章的后续版本<一个href =https://stackoverflow.com/questions/12968351/monad-transformers-vs-passing-parameters-to-functions> Monad变形金刚vs传递参数和,我正在尝试构建两个独立的模块,它们使用Monad变形金刚但暴露Monad-agnostic函数,然后将这些模块中的每个模块的Monad-agnostic函数合并为一个新的Monad-agnostic函数。 我无法运行组合函数,例如如何在下面的例子中使用 子问题是:是否有更好的方法来实现相同的模块化设计目标? 有两个模拟模块(但编译),一个执行日志记录,另一个读取用户输入并对其进行处理。
runReaderT
调用 mainProgram
。
{ - #LANGUAGE FlexibleContexts# - }
$
模块Stackoverflow2其中
导入Control.Monad.Reader
----
----从日志模块 - 将传入的消息写入log
----
data LogConfig = LC {logFile :: FilePath}
doLog ::(MonadIO m,MonadReader LogConfig m)=>字符串 - > m()
doLog _ =未定义
----
----从用户处理模块 - 读取用户输入并将其更改为已配置的案例
----
data MessageCase = LowerCase | UpperCase派生(显示,读取)
getUserInput ::(MonadReader MessageCase m,MonadIO m)=> m字符串
getUserInput =未定义
----
----结合了两个
----
$ b $的主程序b mainProgram ::(MonadReader MessageCase m,MonadReader LogConfig m,MonadIO m)=> m()
mainProgram = do输入< - getUserInput
doLog输入
liftIO $ putStrLn $输入记录:++输入
mainProgram
签名是有问题的,因为 MonadReader
typeclass包含函数依赖项 MonadReader rm | m - > [R
。这基本上意味着单个具体类型不能有多个不同类型的 MonadReader
实例。因此,当您说类型 m
同时包含实例 MonadReader MessageCase
和 MonadReader LogConfig
违背了依赖声明。
最简单的解决方案是将 mainProgram
更改为非通用类型:
mainProgram :: ReaderT MessageCase(ReaderT LogConfig IO)()
mainProgram = do input< ; - getUserInput
lift $ doLog输入
liftIO $ putStrLn $输入记录:++输入
这还需要对 doLog
显式 lift
。
现在可以通过分别运行每个 ReaderT
来运行 mainProgram
,如下所示:
main :: IO()
main = do
let messageCase = undefined :: MessageCase
logConfig = undefined :: LogConfig
runReaderT(runReaderT mainProgram messageCase)logConfig
$ c $如果你想有一个使用两个不同的 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
然而,这具有不幸的效果,即该函数不再是完全通用的,因为两个读者出现在monad堆栈中的顺序被锁定。也许有一个更清晰的方法来实现这一目标,但是我不能在不牺牲(甚至更多)通用性的情况下从我的头顶弄出一个。
I am trying to come up with a modular program design and I, once again, kindly request your help.
As a follow-up to these following posts Monad Transformers vs passing Parameters and Large Scale Design in Haskell, I am trying to build two independent modules that use Monad Transformers but expose Monad-agnostic functions, then combine a Monad-agnostic function from each of these modules into a new Monad-agnostic function.
I have been unable to run the combining function e.g. how do I call mainProgram
using runReaderT
in the example below ?.
The subsidiary question is: is there a better way to achieve the same modular design goal ?
The example has two mock modules (but compiles), one that performs logging and one that reads an user input and manipulates it. The combining function reads the user input, logs it and prints it.
{-# 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
解决方案 Your mainProgram
signature is problematic, because the MonadReader
typeclass contains the functional dependency MonadReader r m | m -> r
. This essentially means that a single concrete type cannot have a MonadReader
instance for multiple different types. So when you say that the type m
has both instances MonadReader MessageCase
and MonadReader LogConfig
it goes against the dependency declaration.
The easiest solution is to change mainProgram
to have a non-generic type:
mainProgram :: ReaderT MessageCase (ReaderT LogConfig IO) ()
mainProgram = do input <- getUserInput
lift $ doLog input
liftIO $ putStrLn $ "Entry logged: " ++ input
This also requires the explicit lift
for doLog
.
Now you can run the mainProgram
by running each ReaderT
separately, like this:
main :: IO ()
main = do
let messageCase = undefined :: MessageCase
logConfig = undefined :: LogConfig
runReaderT (runReaderT mainProgram messageCase) logConfig
If you want to have a generic function that uses two different MonadReader
instances, you need to make it explicit in the signature that one reader is a monad transformer on top of the other reader.
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
However, this has the unfortunate effect that the function is no longer fully generic, because the order in which the two readers appear in the monad stack is locked. Maybe there is a cleaner way to achieve this, but I wasn't able to figure one out from the top of my head without sacrificing (even more) genericity.
这篇关于模块化程序设计 - 在Monad不可知论功能中结合Monad变换器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!