为什么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的东西?
答案 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流。它可以很容易地变成Functor
和Applicative
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课程