首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >对Haskell Monad Transformers的困惑

对Haskell Monad Transformers的困惑
EN

Stack Overflow用户
提问于 2018-08-16 02:13:43
回答 1查看 206关注 0票数 4

我很困惑m应该放在Monad transformers的右边的什么地方?

例如:

WriterT被定义为

代码语言:javascript
复制
newtype WriterT w m a = WriterT { runWriterT :: m (a, w) }

ReaderT被定义为

代码语言:javascript
复制
newtype ReaderT r m a = ReaderT { runReaderT :: r -> m a }

但不是

代码语言:javascript
复制
newtype ReaderT r m a = ReaderT { runReaderT :: m (r -> a) }
EN

回答 1

Stack Overflow用户

发布于 2018-08-16 05:21:42

monad m的位置将取决于应用于底层monad m的monad转换器的功能和操作,因此这取决于读取器和写入器应该向monad添加什么功能。

请记住,runReaderTrunWriterT实际上并没有做任何事情,尽管它们的名称具有提示性。他们只是拆开了一个新的类型,而正是他们包装的东西正在改变着单体m

我的意思是,给定一个单体m,您可以通过考虑以下类型的单体操作来向其添加阅读器:

代码语言:javascript
复制
r -> m a

您可以通过考虑以下类型的一元操作来向其添加编写器:

代码语言:javascript
复制
m (a, w)

您可以通过考虑以下类型的一元操作来向其添加读取器、写入器和状态:

代码语言:javascript
复制
r -> s -> m (a, s, w)

(也就是说,您不需要任何转换器包装器来完成此操作,尽管它们可以使其更方便,特别是因为您可以使用现有的运算符,如>>=<*>,而不必定义自己的运算符。)

因此,当您将阅读器添加到monad m时,为什么不将m放在开头,并考虑以下类型的一元操作?

代码语言:javascript
复制
m (r -> a)

实际上,您可以这样做,但您很快就会发现,这种添加阅读器的方法实际上并没有为monad m添加太多功能。

例如,假设您正在编写一个应该在值表中查找键的函数,并且您希望在读取器中携带该表。由于查找可能会失败,因此您希望在Maybe monad中执行此操作。所以,你想写一些类似这样的东西:

代码语言:javascript
复制
myLookup :: Key -> Maybe Value
myLookup key = ...

但是,您希望使用一个读取器来增强Maybe monad,该读取器提供键和值表。如果我们使用m (r -> a)模式这样做,我们会得到:

代码语言:javascript
复制
myLookup :: Key -> Maybe ([(Key,Value)] -> Value)

现在,让我们尝试实现它:

代码语言:javascript
复制
myLookup k = Just (\tbl -> ...)

我们已经看到了一个问题。在允许我们编写代码访问\tbl之前,我们必须提供一个Just (表示查找已成功)。也就是说,一元操作(返回值为failure或success )不能依赖于r中的信息,这些信息在签名m (r -> a)中应该是显而易见的。使用备用r -> m a模式功能更强大:

代码语言:javascript
复制
type M a = ([Key,Value]) -> Maybe a
myLookup :: Key -> M Value
myLookup key tbl = Prelude.lookup key tbl

@Thomas_M_DuBuisson给出了另一个例子。如果我们试图读取一个输入文件,我们可能会这样写:

代码语言:javascript
复制
readInput :: FilePath -> IO DataToProcess
readInput fp = withFile fp ReadMode $ \h -> ...

在阅读器中携带配置信息(如文件路径)会很好,因此让我们使用模式m (r -> a)将其转换为:

代码语言:javascript
复制
data Config = Config { inputFile :: FilePath }
readConfig :: IO (Config -> DataToProcess)
readConfig = ...um...

我们被卡住了,因为我们不能写一个依赖于配置信息的IO操作。如果我们使用备用模式r -> m a,我们将被设置为:

代码语言:javascript
复制
type M a = Config -> IO a
readConfig :: M DataToProcess
readConfig cfg = withFile (inputFile cfg) ReadMode $ ...

@cdk提出的另一个问题是,这个新的"monadic“操作类型:

代码语言:javascript
复制
m (r -> a)

甚至都不是单调的。它比较弱(只是一个应用性的)。

请注意,向monad添加一个仅适用的阅读器仍然是有用的。它只需要在计算结构不依赖于r中的信息的计算中使用。(因此,如果底层monad为Maybe以允许计算发出错误信号,则可以在计算中使用来自r的值,但计算是否成功的判断必须独立于r。)

但是,r -> m a版本的功能更强大,既可以用作单行阅读器,也可以用作应用型阅读器。

请注意,一些一元转换在多种形式下都很有用。例如,您可以通过两种方式(但仅在有时,@luqui在评论中指出)将编写器添加到m monad中:

代码语言:javascript
复制
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 (例如,错误日志)不能依赖于执行IO操作的结果!同样,(IO a, w)实际上也不是monad;它只是一个应用程序。

另一方面,如果mMaybe,则无论计算成功还是失败,(Maybe a, w)都会写入一些内容,而如果Maybe (a, w)返回Nothing,则会丢失所有日志条目。这两种形式都是单体,在不同的情况下都很有用,它们对应于以不同的顺序堆叠转换器:

代码语言:javascript
复制
MaybeT (Writer w)  -- acts like  (Maybe a, w)
WriterT w Maybe    -- acts like  Maybe (a, w)

对于以不同顺序堆叠MaybeReader的情况也是如此,不是真的。这两者都与“好的”阅读器r -> Maybe a同构。

代码语言:javascript
复制
MaybeT (Reader r)
ReaderT r Maybe
票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/51864209

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档