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等等提供了两个好处:
- 它阻止了一个更高级别的函数必须保持它的types签名所需的所有参数,它调用的(子)函数不是自己使用的(参见
getUserInput
函数) - 从而使更高级别的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 ()
如果我们决定由于某种原因想要改变底层的Program
types,那么这为我们提供了很大的灵活性。 例如,如果我们想添加可修改的状态到我们的程序中,我们可以重新定义
data ProgramState = PS Int Int Int type Program a = StateT ProgramState (ReaderT Config IO) a
我们不必修改getUserInput
或runProgram
– 它们将继续正常工作。
注意我没有input检查这个post,更别说试图运行它了。 可能有错误!