我对m
应该放在Monad变压器右侧的位置感到困惑吗?
例如:WriterT
定义为
newtype WriterT w m a = WriterT { runWriterT :: m (a, w) }
而
ReaderT
被定义为newtype ReaderT r m a = ReaderT { runReaderT :: r -> m a }
但不是
newtype ReaderT r m a = ReaderT { runReaderT :: m (r -> a) }
最佳答案
monad m
的位置将取决于应用于基础monad m
的monad转换器的功能和操作,因此,它由读写器应该添加到monad的功能确定。
记住runReaderT
和runWriterT
尽管具有暗示性的名称,实际上并没有做任何事情。他们只是解包一个新类型,正是它们包装的东西改变了monad m
。
我的意思是,给定monad m
,您可以通过考虑类型的monadic动作向其添加读者:
r -> m a
并且您可以通过考虑以下类型的单子动作向其添加编写器:
m (a, w)
并且您可以通过考虑以下类型的单子动作来向其添加阅读器,作者和状态:
r -> s -> m (a, s, w)
(也就是说,您不需要任何变压器包装器,尽管它们可以使它更加方便,尤其是因为您可以使用现有的运算符,例如
>>=
和<*>
,而不必定义自己的运算符。)因此,当您将阅读器添加到monad
m
时,为什么不将m
放在开头并考虑以下类型的monadic动作呢?m (r -> a)
实际上,您可以这样做,但是您很快就会发现,这种添加阅读器的方法实际上并没有为monad
m
添加太多功能。例如,假设您正在编写一个应在值表中查找键的函数,并且希望将该表携带在读取器中。由于查找可能失败,因此您想在
Maybe
monad中执行此操作。因此,您想要编写如下内容:myLookup :: Key -> Maybe Value
myLookup key = ...
但是,您想使用提供键和值表的阅读器来增强
Maybe
monad。如果使用m (r -> a)
模式执行此操作,则会得到:myLookup :: Key -> Maybe ([(Key,Value)] -> Value)
现在,让我们尝试实现它:
myLookup k = Just (\tbl -> ...)
我们已经看到了一个问题。在允许我们编写代码以访问
Just
之前,我们必须提供一个\tbl
(指示查找成功)。也就是说,单例操作(失败或带有返回值的成功)不能依赖于r
中的信息,这些信息从签名m (r -> a)
中应该是显而易见的。使用备用r -> m a
模式更强大:type M a = ([Key,Value]) -> Maybe a
myLookup :: Key -> M Value
myLookup key tbl = Prelude.lookup key tbl
@Thomas_M_DuBuisson给出了另一个示例。如果我们尝试读取输入文件,则可以编写:
readInput :: FilePath -> IO DataToProcess
readInput fp = withFile fp ReadMode $ \h -> ...
最好在阅读器中随身携带诸如文件路径之类的配置信息,所以让我们使用模式
m (r -> a)
将其转换为:data Config = Config { inputFile :: FilePath }
readConfig :: IO (Config -> DataToProcess)
readConfig = ...um...
并且因为无法编写依赖于配置信息的IO操作而陷入困境。如果我们使用替代模式
r -> m a
,则将设置为:type M a = Config -> IO a
readConfig :: M DataToProcess
readConfig cfg = withFile (inputFile cfg) ReadMode $ ...
@cdk引发的另一个问题是这种新的“单子”动作类型:
m (r -> a)
甚至都不是单子。它较弱(仅适用于应用)。
请注意,将仅适用的阅读器添加到monad仍然有用。它只需要用于计算结构不依赖
r
中的信息的计算中即可。 (因此,如果基础monad为Maybe
以允许计算发出错误信号,则可以将r
中的值用于计算中,但确定计算是否成功必须独立于r
。 )但是,
r -> m a
版本严格更强大,可以用作单例和应用程序阅读器。请注意,一些单调变换可以多种形式使用。例如,您可以(但仅在某些情况下,如@luqui在评论中指出的那样)以两种方式将作家添加到
m
monad中:m (a, w) -- if m is a monad this is always a monad
(m a, w) -- this is a monad for some, but not all, monads m
如果
m
是IO
,则IO (a,w)
比(IO a, w)
有用-对于后者,写入的w
(例如,错误日志)不能取决于执行< cc>行动!同样,IO
实际上不是单子。这只是一个应用。另一方面,如果
(IO a, w)
是m
,则Maybe
会写一些内容,无论计算成功还是失败,而(Maybe a, w)
如果返回Maybe (a, w)
,则会丢失所有日志条目。两种形式都是单子形式,可以在不同情况下使用,它们分别对应于以不同顺序堆叠变压器:MaybeT (Writer w) -- acts like (Maybe a, w)
WriterT w Maybe -- acts like Maybe (a, w)
对于以不同顺序堆叠
Nothing
和Maybe
而言,情况并非如此。这两个都是“好”阅读器Reader
的同构:MaybeT (Reader r)
ReaderT r Maybe
关于haskell - 对Haskell Monad变形金刚感到困惑,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51864209/