我很困惑m应该放在Monad transformers的右边的什么地方?
例如:
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) }发布于 2018-08-16 05:21:42
monad m的位置将取决于应用于底层monad m的monad转换器的功能和操作,因此这取决于读取器和写入器应该向monad添加什么功能。
请记住,runReaderT和runWriterT实际上并没有做任何事情,尽管它们的名称具有提示性。他们只是拆开了一个新的类型,而正是他们包装的东西正在改变着单体m。
我的意思是,给定一个单体m,您可以通过考虑以下类型的单体操作来向其添加阅读器:
r -> m a您可以通过考虑以下类型的一元操作来向其添加编写器:
m (a, w)您可以通过考虑以下类型的一元操作来向其添加读取器、写入器和状态:
r -> s -> m (a, s, w)(也就是说,您不需要任何转换器包装器来完成此操作,尽管它们可以使其更方便,特别是因为您可以使用现有的运算符,如>>=和<*>,而不必定义自己的运算符。)
因此,当您将阅读器添加到monad m时,为什么不将m放在开头,并考虑以下类型的一元操作?
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 -> ...)我们已经看到了一个问题。在允许我们编写代码访问\tbl之前,我们必须提供一个Just (表示查找已成功)。也就是说,一元操作(返回值为failure或success )不能依赖于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提出的另一个问题是,这个新的"monadic“操作类型:
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 (例如,错误日志)不能依赖于执行IO操作的结果!同样,(IO a, w)实际上也不是monad;它只是一个应用程序。
另一方面,如果m为Maybe,则无论计算成功还是失败,(Maybe a, w)都会写入一些内容,而如果Maybe (a, w)返回Nothing,则会丢失所有日志条目。这两种形式都是单体,在不同的情况下都很有用,它们对应于以不同的顺序堆叠转换器:
MaybeT (Writer w) -- acts like (Maybe a, w)
WriterT w Maybe -- acts like Maybe (a, w)对于以不同顺序堆叠Maybe和Reader的情况也是如此,不是真的。这两者都与“好的”阅读器r -> Maybe a同构。
MaybeT (Reader r)
ReaderT r Maybehttps://stackoverflow.com/questions/51864209
复制相似问题