haskell - 对Haskell Monad变形金刚感到困惑

标签 haskell monad-transformers

我对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的功能确定。

记住runReaderTrunWriterT尽管具有暗示性的名称,实际上并没有做任何事情。他们只是解包一个新类型,正是它们包装的东西改变了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


如果mIO,则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)


对于以不同顺序堆叠NothingMaybe而言,情况并非如此。这两个都是“好”阅读器Reader的同构:

MaybeT (Reader r)
ReaderT r Maybe

关于haskell - 对Haskell Monad变形金刚感到困惑,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51864209/

相关文章:

haskell - 将错误值提升到 ErrorT monad 转换器

Haskell:为什么这个 monad 转换是错误的?

parsing - 底层秒差距单子(monad)

haskell - Haskell 有没有一种方法可以检查一个模块是否导出与另一个模块相同的函数?

Haskell 光泽库,如何在模型签名上运行appendFile以将日志记录到文件?

haskell - 'in' 关键字是怎么回事?

haskell - 试图了解单子(monad)变压器产生的类型

list - 如何在 Haskell 中切换列表中的 2 个元素

haskell - 子网站类型之谜

haskell - 写一个Monad Transformer,真的需要这么多硬编码的实例吗