所以在变形金刚
中我明白了,
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
值的生成者调用 throwError
和 return
,而不是 Nothing
和 Just
.
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 withreturn :: a -> m a
) orIO
?
正如你所说,最常见的基础是 IO
(为此我们已经有了 MonadIO
)或 Identity
(对于它通常只需使用 return 和纯计算而不是提升的单子(monad)计算)。有时,ST
是一个方便的基本 monad,但在 ST
上使用变压器比在 IO
上使用变压器要少一些。
关于haskell - 为什么 MonadIO 是特定于 IO 的,而不是更通用的 MonadTrans?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38212294/