6

为什么不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 are not monad transformersconstrained to yield monads中的建议不同)并确保结果始终是 monad。允许单子转换器产生不是单子的东西有什么原因吗?

4

3 回答 3

6

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

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 流。它可以很容易地制成aFunctor和anApplicative

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

但显然不是单子。所以我们有一个MonadTrans实例可以将 monad 转换为仅Applicative.

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

(整件事让我意识到,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
于 2013-08-28T18:11:14.697 回答
4

我的猜测是 aMonadTrans将 aMonad转换为其他东西,而不是将 aMonad转换为 a Monad。它更通用,因为您可能会编写一些转换 aMonad并且您可以定义的东西lift,但您不能定义>>=and return。由于大多数(如果不是全部)MonadTrans实例最终都是Monads,因此它并没有真正出现问题,因为编译器仍然可以很好地处理它。

于 2013-08-28T17:13:26.350 回答
3

I'm going to disagree with the other two answers to say that the result should be a monad. The reason why is that otherwise there are no sensible laws that lift should obey.

lift is supposed to be a monad morphisms, meaning that it should obey the following two laws:

lift (return x) = return x

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

These laws make more sense when you realize they are functor laws between two Kleisli categories:

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

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

However, if you don't constrain the output to be a monad then those laws are no longer valid and you have no sensible way to verify that you implemented lift correctly.

I suspect the real reason was to make the class Haskell98

于 2013-08-28T20:19:23.747 回答