haskell - 为什么 MonadIO 是特定于 IO 的,而不是更通用的 MonadTrans?

标签 haskell types io monad-transformers

所以在变形金刚中我明白了,

class (Monad m) => MonadIO m where
    -- | Lift a computation from the 'IO' monad.
    liftIO :: IO a -> m a

instance MonadIO IO where
    liftIO = id

据我了解,这与 MonadTrans 不同的原因是,如果您有一些由 4 个组成的 monad 变压器组成的 M1T (M2T (M3T (M4T IO))) x,那么您就不需要不想举起 。举起 。举起 。 lift $ putStrLn "abc" 但您宁愿只是 liftIO $ putStrLn "abc"

但是,当上面的基本定义似乎是带有 liftIO 的一组奇怪的递归时,IO 的这种特殊性似乎非常奇怪。似乎应该有一些组合器的新类型声明,例如 (ExceptT :~: MaybeT) IO x ,这样单个 lift 就是您所需要的(我想这是一个 monad 变压器变压器?),或者是一些多参数类型类,

class (Monad m) => MonadEmbed e m
    -- | Lift a computation from the `e` monad.
    embed :: e a -> m a

instance (Monad m) => MonadEmbed m m where
     embed = id

为什么 transformers 不使用其中一种方法,这样 MonadTrans 序列就不必 Root 于 IO 中?事实上,变压器处理所有“其他”效果,以便最底层的唯一东西是 Identity (已经用 return::a -> m a 处理) >) 还是IO?或者上面是否需要像 UndecidableInstances 这样的东西,而 Transformers 库不愿意包含这些东西?或者什么?

最佳答案

But, this specificity for IO seems very weird

我质疑这是特定于 IO 的假设。我还在 mtl 中看到了许多其他类。例如:

class Monad m => MonadError e m | m -> e where
    throwError :: e -> m a
    catchError :: m a -> (e -> m a) -> m a

class Monad m => MonadState s m | m -> s where
    get :: m s
    put :: s -> m ()
    state :: (s -> (a, s)) -> m a

...以及许多其他。一般来说,构建一元操作的“mtl方式”是使用这些类型类多态操作,这样就不需要提升——相反,只需将操作单态化为适当的提升类型。例如,MonadError 完全替换了假设的 liftMaybe::MonadMaybe m => Maybe a -> m a:而不是提升 Maybe a 值,我们可以让 Maybe a 值的生成者调用 throwErrorreturn,而不是 NothingJust .

It seems like there should be a newtype declaration for some combinator like (ExceptT :~: MaybeT) IO x so that a single lift is all you ever need

根据此提案,您(至少)需要两种不同类型的电梯:一台电梯从m a前往trans m a ,以及从 trans m a(trans' :~: trans) m a 的一部电梯。处理两种提升的单个操作更加统一。

It seems like there should be some multi-param type class,

class Monad m => MonadEmbed e m
    -- | Lift a computation from the `e` monad.
    embed :: e a -> m a

instance Monad m => MonadEmbed m m where
    embed = id

这种方法乍一看似乎很不错。但是,如果您尝试编写和使用此类,您很快就会发现为什么所有 mtl 类都包含功能依赖项:实例 MonadEmbed m m 令人惊讶地难以选择!即使是一个非常简单的例子,例如

embed (Right ()) :: Either String ()

是一个歧义错误。 (毕竟,我们只知道 Right 3::Either a () 对于某些 a ——我们还不知道 a ~ String code>,因此我们无法选择 MonadEmbed m m 实例!)我怀疑您的大多数其他实例都会遇到类似的问题。如果您添加明显的函数依赖项,您的类型推断问题就会消失,但 Fundep 检查会极大地限制您:人们可能只能从基础 monad 中提升,而不是像您希望的那样从任意中间 monad 中提升。这在实践中是一个非常痛苦的问题(并且“mtl方式”的痛苦是如此之小),以至于它在mtl中没有完成。

也就是说,您可能喜欢使用 transformers-base包。

Is it just the fact that the transformers handle all "other" effects so that the only things at the very bottom are either Identity (already handled with return :: a -> m a) or IO?

正如你所说,最常见的基础是 IO (为此我们已经有了 MonadIO)或 Identity (对于它通常只需使用 return 和纯计算而不是提升的单子(monad)计算)。有时,ST 是一个方便的基本 monad,但在 ST 上使用变压器比在 IO 上使用变压器要少一些。

关于haskell - 为什么 MonadIO 是特定于 IO 的,而不是更通用的 MonadTrans?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38212294/

相关文章:

data-structures - Haskell 的代数数据类型

haskell - Haskell 中嵌套 `do` block

haskell - 如何在Haskell中创建包含有限长度字符串的类型

typescript - 类型谓词在 'expanded' 过滤器函数中不起作用

Java:从文件中读取最多 x 个字符到数组中

c - putchar() 奇怪的输出,为什么会这样?

haskell - 如何找到最优的加工顺序?

Java AST 解析器 - 获取参数的完整类型、外部项目

c++ - 计算类型范围

java - 使用 JUnit 测试多线程代码 - 奇怪的行为