为什么`>>`的列表monad方法不能定义为`flip const`?

时间:2012-05-08 00:13:05

标签: haskell monads

为什么Prelude没有像这样定义列表monad? (注意>>的非标准实现。)

instance  Monad []  where
    m >>= k          = concat (map k m)
    m >> k           = k                 -- a.k.a. flip const
    return x         = [x]
    fail s           = []

我尝试根据monad法律对此进行检查,但他们没有提到>>Monad类定义是这样的:

m >> k = m >>= \_ -> k

[]实例中的哪一个会转换为:

concat (map (\_ -> k) m)

当然不等同于flip const - 它们会产生明显不同的结果,例如[1..5] >> return 1。但我不清楚这个默认定义是 law Monad的实例必须尊重的,还是只是一个默认的实现,它满足flip const实现的其他一些法律也满足。

直观地说,鉴于列表monad的 intent (“非确定性计算”),似乎>>的替代定义同样好,如果不是更好,多亏了修剪保证等于一个的分支。或者另一种说法是,如果我们处理而不是列表,那么两个候选定义将是等价的。但是我在这里遗漏了一些微妙的内容,导致列表的flip const定义错误吗?

编辑:ehird的答案在上面引发了一个非常明显的缺陷,即[] >> k得到错误的预期结果,应该是[],而不是k {1}}。不过,我认为这个问题可以修改为这个定义:

[] >> k = []
_ >> k = k

3 个答案:

答案 0 :(得分:14)

a >> b必须始终等同于a >>= const b;它只在Monad类中,因此您可以定义更高效(但语义等效的)版本。这就是为什么它不是monad定律的一部分:它不是monad定义的一部分,只是类型类(如fail)。

不幸的是,我在基本软件包的文档中找不到明确说明的内容,但我认为旧版本可能在(>>)类型类之外定义了Monad

对于它的价值,你对(>>)的定义打破了列表monad用于非确定性计算的用法。由于[]用于表示失败,[] >> m必须始终为[];在你耗尽所有可能的分支后,你不能继续!这也意味着这两个计划:

do { m; ... }
do { _ <- m; ... }

可能会有不同的行为,因为前者使用(>>)进行后瞻,后者使用(>>=)。 (参见Haskell 2010 Report。)

答案 1 :(得分:4)

因为ma >> mb只是 ma >>= \_ -> mb的简写。如果>>仅在列表的情况下被定义为flip const,则ma >> mb将无法在ma 中运行所有中的计算{ {1}}是一个非常混乱的列表。

至于为什么它一般不被定义为ma ,那么flip const的当前语义使它能够在你不关心的地方对序列进行排序关于他们的结果,这通常是有用的。

答案 2 :(得分:2)

您对>>的定义违反了Monad相关性法:

newtype B a = B { unB :: [a] }

instance Monad B where
  m >>= f = B . concatMap (unB.f) $ unB m
  (>>) = flip const
  return a = B [a]

case1 = B [1,2,3] >>= (\_ -> B [4,5,6] >> return 1)
case2 = (B [1,2,3] >>= \_ -> B [4,5,6]) >> return 1

main = do
  print $ unB case1
  print $ unB case2

上述两种情况的相关性不同,但结果不同。