为什么电梯的返回值不能限制为monad?

时间:2013-08-28 17:02:49

标签: haskell monads typeclass monad-transformers

为什么MonadTrans未定义为

class MonadTrans t where
    lift :: (Monad m, Monad (t m)) => m a -> t m a
--                    ^^^^^^^^^^^

而不是当前

class MonadTrans t where
    lift :: Monad m => m a -> t m a

这是Haskell 98(与Why aren't monad transformers constrained to yield monads?中的建议不同)并确保结果始终为monad。是否有理由允许monad变换器生产不是monad的东西?

3 个答案:

答案 0 :(得分:6)

bheklilr的回答让我想到了一个monad变换器产生的东西不是monad的例子。一个不是monad的着名例子是ZipList。我们可以制作一个在每个级别运行monadic动作的变体:

import Control.Applicative
import Control.Arrow ((***))
import Control.Monad
import Control.Monad.Trans

-- | A list where each step is produced by a monadic action.
data ListT m a = Nil | Cons (m (a, ListT m a))

这实际上是一个monad流。它可以很容易地变成FunctorApplicative

instance Monad m => Functor (ListT m) where
    fmap f Nil      = Nil
    fmap f (Cons k) = Cons $ (f *** fmap f) `liftM` k
instance Monad m => Applicative (ListT m) where
    pure x = Cons $ return (x, pure x)
    Cons mf <*> Cons mx = Cons $ do
        (f, fs) <- mf
        (x, xs) <- mx
        return (f x, fs <*> xs)
    _       <*> _       = Nil

但显然不是monad。所以我们有一个MonadTrans实例,可以将monad转换为只有Applicative的内容。

instance MonadTrans ListT where
    lift mx = Cons $ (\x -> (x, lift mx)) `liftM` mx

(这一切让我意识到 conduit-extra 中的实验ZipSink也是一个不错的例子。)


然而,这引出了另一个问题:如果我们想要这样的变形金刚,他们应该遵守什么法律? MonadTrans的法律定义为

lift . return = return
lift (m >>= f) = lift m >>= (lift . f)

因此,在我们的案例中,我们希望得到像

这样的东西
lift (f `liftM` x)  = fmap f (lift x)

lift . return       = pure
lift (m `ap` f)     = lift m <*> lift f

答案 1 :(得分:4)

我的猜测是,MonadTrans会将Monad转换为其他内容,而不是将Monad转换为Monad。它更通用,因为您可能会编写转换Monad的内容并且您可以定义lift,但您无法定义>>=return。由于大多数(如果不是全部)MonadTrans实例最终都是Monad s,因此编译器仍然可以正常处理它并不会产生问题。

答案 2 :(得分:3)

我不同意其他两个答案,说结果应该是monad。原因是否则lift应该遵守没有合理的法律。

lift应该是monad态射,意味着它应该遵守以下两个定律:

lift (return x) = return x

lift (m >>= f) = lift m >>= \r -> lift (f r)

当你意识到它们是两个Kleisli类别之间的仿函数法时,这些定律更有意义:

-- i.e. "fmap id = id"
(lift .) return = return

-- i.e. "fmap (f . g) = fmap f . fmap g"
(lift .) (f >=> g) = (lift .) f >=> (lift .) g

但是,如果您不将输出限制为monad,则这些法律不再有效,并且您没有合理的方法来验证您是否正确实现了lift

我怀疑真正的原因是制作Haskell98课程