模块化程序设计 - 在Monad不可知论功能中结合Monad变换器 [英] Modular Program Design - Combining Monad Transformers in Monad Agnostic functions

查看:65
本文介绍了模块化程序设计 - 在Monad不可知论功能中结合Monad变换器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图想出一个模块化的程序设计,我再次请求您的帮助。



作为以下文章的后续版本<一个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
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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆