【问题标题】:Why isn't lift's return value constrained to be a monad?为什么电梯的返回值不被限制为单子?
【发布时间】:2013-08-28 17:02:48
【问题描述】:

为什么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 的东西是有原因的吗?

【问题讨论】:

  • 为什么要确保结果是 monad?这将是严格意义上的不那么普遍,我看不出它有什么好处。
  • @JohnL 主要是因为MonadTrans laws 以单子形式表示,因此结果必须是单子。如果不是,则法律甚至无法表达。但是 bheklilr 在他的回答中提出了一个很好的观点,并基于此,我制作了一个 example,它产生了一个 Monad -> Applicative 变压器。然而,这意味着我们需要制定一套不同的法律。

标签: haskell monads typeclass monad-transformers


【解决方案1】:

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

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

但显然不是单子。所以我们有一个 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

【讨论】:

  • 请注意,如果您将 ListT 包装在最后一个 monad 层中,那么它就是一个 monad,特别是 "ListT done right"
  • @GabrielGonzalez 是的,记住ListT 是我的灵感。但是我的ListTpure&lt;*&gt; 的定义不同,就像ZipList[] 不同一样。所以它没有有效的 monad 实例。
【解决方案2】:

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

【讨论】:

  • 没有个人知识,但我想这就是原因。您可能想要一个MonadTrans 实例来代替FunctorApplicative 而不是Monad,这似乎是合理的。
  • @JohnL 这正是我的推理。我没有引用任何来源,因为我不知道任何来源(因此,如果有人有该文档,请随时发布它),但 MonadTrans 仅在单个 monad 上运行是有道理的,而不是两个。
【解决方案3】:

我不同意其他两个答案,说结果应该是一个单子。原因是,否则lift 不应该遵守任何明智的法律。

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

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 类

【讨论】:

  • 我对@9​​87654326@ 的想法似乎是H98。所以你建议对于像ZipListT 这样的东西,我们应该使用另一个像lift :: (Applicative f) =&gt; f a -&gt; t f a 这样的应用转换器?
  • @PetrPudlák 我不知道缺乏约束的真正原因,所以我只是猜测。应用转换器会遵守哪些法律?
  • 我想一个应用转换器应该遵守应用定律,而一个函子转换器只需要遵守函子定律。虽然它很可能是一个尖的函子,但lift . pure = pure 应该仍然有效。我认为这已经足够了。
  • @GabrielGonzalez 我在自己的答案末尾提出了一些法律(只是猜测)。
  • @PetrPudlák 我喜欢这些法律。请注意,由于参数性,第一定律自动为真,但其他两个很好。
猜你喜欢
  • 1970-01-01
  • 2014-04-21
  • 1970-01-01
  • 1970-01-01
  • 2019-02-11
  • 2017-04-03
  • 2013-02-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多