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

时间:2016-07-05 20:36:36

标签: haskell types io monad-transformers

所以在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不同的原因是,如果你有一些M1T (M2T (M3T (M4T IO))) x由4个组成的monad变换器组成,那么你不想lift . lift . lift . lift $ putStrLn "abc"但是你&#39 ;而只是liftIO $ putStrLn "abc"

但是,当上面的基本定义似乎是IO这种奇怪的递归时,liftIO的这种特异性似乎很奇怪。似乎应该为(ExceptT :~: MaybeT) IO x这样的组合器设置一个newtype声明,这样你就可以得到一个lift(我想这是一个monad变换器变换器吗?),或者是多个param类,

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序列不必植根于IO?是变压器处理所有"其他"效果,以便最底层的唯一内容是Identity(已经使用return :: a -> m a处理)或IO?或者上面是否需要像transformers库那样无法包含的UndecidableInstances?或者是什么?

2 个答案:

答案 0 :(得分:7)

  

但是,IO的这种特殊性似乎非常奇怪

我质疑这是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方式"构建monadic动作就是使用这些类型类多态操作,这样就不需要lift - 而只需将操作单态化为适当的提升类型。例如,MonadError完全取代了假设的liftMaybe :: MonadMaybe m => Maybe a -> m a:而不是提升Maybe a值,可以让Maybe a值调用throwError的生产者和{ {1}}代替returnNothing

  

似乎应该为Just之类的组合器提供新类型声明,以便您只需要一次升力

根据此提案,您需要(至少)两种不同的升降机:一部电梯从(ExceptT :~: MaybeT) IO x升级到m a,一部升降机从trans m atrans m a。只需一次操作即可处理两种提升方式。

  

似乎应该有一些多参数类型,

(trans' :~: trans) m a

这种方法起初看起来很不错。但是,如果您尝试编写并使用此类,您将很快发现为什么所有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 } instance!)我怀疑你的大多数其他实例都会遇到类似的问题。如果你添加明显的函数依赖关系,你的类型推理问题就会消失,但是fundep检查会对你产生很大的限制:你可能只会从基础monad中提升,而不是像你希望的那样从任意中间monad中解除。这在实践中是一个非常痛苦的问题(" MonadEmbed m m方式"痛苦是如此之小),以至于mtl没有完成。

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

  

事实上,变压器处理所有"其他"效果,以便最底层的唯一内容是mtl(已经使用Identity处理)或return :: a -> m a

正如你所说,最常见的基础是IO(我们已经为IO}或MonadIO(其中一个通常仅使用Identity和纯return计算而不是提升的monadic计算)。有时ST是一个方便的基础monad,但在ST上使用变换器比在IO上使用变换器更为罕见。

答案 1 :(得分:5)

Monad...类似乎主要设计为直接概括 monad的特征操作,如果该monad被隐藏在一堆变换器下,也可以工作。这些操作通常不是那么大,例如如果您愿意,State只需要getputstatemodify,但就是这样。

IO不是这样;制作MonadIO类的过多IO基本IO操作方法是相当不切实际的。你当然可以获得所有,如果你只是将liftIO作为“转换函数”引入,但是我打赌这总是被认为是一个黑客攻击。

此外:IO是最重要的非平凡基础单子;仅仅因为这个原因,我不认为给它一个专门的升力功能是不合适的。

关于你的“单一电梯是你所需要的”想法::~:的问题是变换器现在形成二叉树而不是具有清晰层次结构的简单堆栈。这使得整个mtl类的想法更加成问题。