Monad Transformers vs将parameter passing给函数

我是Haskell的新手,但了解Monad变形金刚如何使用。 然而,我仍然有困难抓住他们声称的优势,而不是将parameter passing给函数调用。

基于维基单片变形金刚解释 ,我们基本上有一个configuration对象定义为

data Config = Config Foo Bar Baz 

并传递给它,而不是写这个签名的函数

 client_func :: Config -> IO () 

我们使用ReaderT Monad Transformer并将签名更改为

 client_func :: ReaderT Config IO () 

拉Config只是一个电话ask

函数调用从client_func c改变到runReaderT client_func c

精细。

但为什么这使我的应用程序更简单?

1 – 我怀疑Monad变形金刚有趣的事情,当你缝​​合很多function/模块一起形成一个应用程序。 但这是我的理解停止的地方。 有人可以摆脱一些光?

2-我找不到有关如何在Haskell中编写大型模块化应用程序的文档,其中模块公开了某种forms的API并隐藏了它们的实现,以及(部分)将其自己的状态和环境隐藏到其他模块中。 任何指针?

(编辑:真实世界哈斯克尔说:“这种方法[Monad变形金刚] …扩展到更大的程序”,但没有明确的例子certificate这一说法)

编辑下面克里斯泰勒答案下面

Chris完美地解释了为什么在Transformer Monad中封装Config,State等等提供了两个好处:

  1. 它阻止了一个更高级别的函数必须保持它的types签名所需的所有参数,它调用的(子)函数不是自己使用的(参见getUserInput函数)
  2. 从而使更高级别的function对变压器Monad内容的变化更有弹性(比如说你想添加一个Writer来提供一个更低级别的function)

这是以改变所有函数的签名为代价的,以便它们在Transformer Monad中运行。

所以问题1被完全覆盖。 谢谢Chris。

问题2现在在这个SOpost中回答

假设我们正在编写一个需要以下forms的configuration信息的程序:

 data Config = C { logFile :: FileName } 

编写程序的一种方法是在函数之间显式传递configuration。 如果我们只需要将它传递给明确使用它的函数就好了,但是我们不确定函数是否需要调用另一个使用该configuration的函数,所以我们不得不把它作为(实际上,它往往是需要使用configuration的低级函数,这迫使我们将它传递给所有的高级函数)。

我们来编写这个程序,然后使用Reader monad重新编写程序,看看我们得到了什么好处。

选项1.显式configuration传递

我们结束了这样的事情:

 readLog :: Config -> IO String readLog (C logFile) = readFile logFile writeLog :: Config -> String -> IO () writeLog (C logFile) message = do x <- readFile logFile writeFile logFile $ x ++ message getUserInput :: Config -> IO String getUserInput config = do input <- getLine writeLog config $ "Input: " ++ input return input runProgram :: Config -> IO () runProgram config = do input <- getUserInput config putStrLn $ "You wrote: " ++ input 

请注意,在高级function中,我们必须始终configuration。

选项2.读卡器monad

另一种方法是使用Reader monad重写。 低级function有点复杂:

 type Program = ReaderT Config IO readLog :: Program String readLog = do C logFile <- ask readFile logFile writeLog :: String -> Program () writeLog message = do C logFile <- ask x <- readFile logFile writeFile logFile $ x ++ message 

但作为我们的奖励,高级function更简单,因为我们永远不需要参考configuration文件。

 getUserInput :: Program String getUserInput = do input <- getLine writeLog $ "Input: " ++ input return input runProgram :: Program () runProgram = do input <- getUserInput putStrLn $ "You wrote: " ++ input 

更进一步

我们可以重写getUserInput和runProgram的types签名为

 getUserInput :: (MonadReader Config m, MonadIO m) => m String runProgram :: (MonadReader Config m, MonadIO m) => m () 

如果我们决定由于某种原因想要改变底层的Programtypes,那么这为我们提供了很大的灵活性。 例如,如果我们想添加可修改的状态到我们的程序中,我们可以重新定义

 data ProgramState = PS Int Int Int type Program a = StateT ProgramState (ReaderT Config IO) a 

我们不必修改getUserInputrunProgram – 它们将继续正常工作。

注意我没有input检查这个post,更别说试图运行它了。 可能有错误!

    Interesting Posts