康纳德在哈斯克尔打破了懒惰

Shi*_* Wu 10 monads continuations haskell

我正在尝试Cont monad,并发现以下问题.

  1. 首先构造一个无限列表并将所有元素提升为Cont monad
  2. 使用序列操作在无限列表中获取Cont monad.
  3. 例如,当我们尝试使用头部运行monad时,它会在尝试扩展延续时陷入无限循环,并且永远不会调用头部.

代码如下所示:

let inff = map (return :: a -> Cont r a) [0..]
let seqf = sequence inff
runCont seqf head
Run Code Online (Sandbox Code Playgroud)

那么这是Haskell中Cont monad实现的限制吗?如果是这样,我们如何改善这一点?

Pet*_*lák 13

其原因是,即使的头元素sequence someList只取决于第一elemenent someList,该效果sequence someList一般可以依靠的全部效应someList(以及它对于大多数的单子).因此,如果我们想要评估head元素,我们仍然需要评估所有效果.

例如,如果我们有一个列表Maybe值,结果sequence someListJust只有当所有的元素someListJust.因此,如果我们尝试sequence无限列表,我们需要检查其无限数量的元素,如果它们都是Just.

这同样适用Cont.在continuation monad中,我们可以随时从计算中转义并返回与到目前为止计算的结果不同的结果.请考虑以下示例:

test :: (Num a, Enum a) => a
test = flip runCont head $
    callCC $ \esc -> do
        sequence (map return [0..100] ++ [esc [-1]])
Run Code Online (Sandbox Code Playgroud)

或直接使用cont而不是callCC:

test' :: (Num a, Enum a) => a
test' = flip runCont head $
            sequence (map return [0..100] ++ [cont (const (-1))])
Run Code Online (Sandbox Code Playgroud)

结果test就是-1.在处理前100个元素之后,最终元素可以决定逃避所有这些并返回-1.所以为了看看是什么head元素sequence someListCont,我们再次需要计算所有.


Gab*_*lez 6

这不是Contmonad 的缺陷sequence.您可以获得类似的结果Either,例如:

import Control.Monad.Instances ()

xs :: [Either a Int]
xs = map Right [0..]  -- Note: return = Right, for Either

ys :: Either a [Int]
ys = sequence xs
Run Code Online (Sandbox Code Playgroud)

ys在计算整个列表之前,您无法检索任何元素,这将永远不会发生.

另外,请注意:sequence (map f xs) = mapM f xs,因此我们可以将此示例简化为:

>>> import Control.Monad.Instances
>>> mapM Right [0..]
<Hangs forever>
Run Code Online (Sandbox Code Playgroud)

有一些monad mapM将在无限的值列表中工作,特别是懒惰的StateTmonad和Identity,但它们是规则的例外.

通常,mapM/ sequence/ replicateM(没有尾随下划线)是反模式并且使用正确的解决方案pipes,这允许您构建有效的流,而不是尝试预先计算所有结果. pipes教程的开头部分描述了如何更详细地解决这个问题,但一般的经验法则是,每当你写下这样的内容时:

example1 = mapM f xs

example2 = sequence xs
Run Code Online (Sandbox Code Playgroud)

只需将其转换为以下内容即可将其转换为惰性Producer:

example1' = each xs >-> Pipes.Prelude.mapM f

example2' = each xs >-> Pipes.Prelude.sequence
Run Code Online (Sandbox Code Playgroud)

使用上面的例子Either,你会写:

>>> import Pipes
>>> let xs = each [0..] >-> mapM Right :: Producer Int (Either a) ()
Run Code Online (Sandbox Code Playgroud)

然后你可以懒惰地处理流而不生成所有元素:

>>> Pipes.Prelude.any (> 10) xs
Right True
Run Code Online (Sandbox Code Playgroud)

  • 我认为将mapM称为反模式通常有点苛刻.我说只有当列表具有无限长度(例如从用户输入派生)时它才是坏的.但是在最多100个元素的列表上调用mapM并没有太大的错误. (6认同)
  • mapM函数根本不是反模式,只不过是反模式的效果. (5认同)
  • 当流O(N)解存在时,使用具有O(N ^ 2)空间和时间复杂度的东西是我眼中的反模式.另外,请注意OP特别将这种行为描述为用他们自己的话来说是有问题的,所以这个陈述在这个问题的上下文中是相关的. (2认同)