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

时间:2014-02-22 02:37:20

标签: haskell monads continuations

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

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

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

    这是Haskell中Cont monad实现的限制吗? 如果是这样,我们如何改进呢?

2 个答案:

答案 0 :(得分:13)

原因是即使sequence someList的head元素的仅取决于someList的第一个元素,效果 sequence someList的{​​{1}}通常可以取决于someList的所有效果(对大多数monad来说都是如此)。因此,如果我们想要评估head元素,我们仍然需要评估所有效果。

例如,如果我们有一个Maybe值列表,则只有sequence someList的所有元素都为Just时,someList的结果才为Just 。因此,如果我们尝试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]])

或直接使用cont代替callCC

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

test的结果只是-1。在处理前100个元素之后,最终元素可以决定逃避所有这些并返回-1。因此,为了查看headsequence someList的{​​{1}}元素是什么,我们再次需要计算它们。

答案 1 :(得分:6)

这不是Cont monad与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

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

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

>>> import Control.Monad.Instances
>>> mapM Right [0..]
<Hangs forever>

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

通常,mapM / sequence / replicateM(没有尾随下划线)是反模式,正确的解决方案是使用pipes,这可以让您构建有效的不尝试预先计算所有结果的流。 The beginning of the pipes tutorial描述了如何更详细地解决这个问题,但一般的经验法则是,只要你写下这样的内容:

example1 = mapM f xs

example2 = sequence xs

只需将其转换为:

,即可将其转换为惰性Producer
example1' = each xs >-> Pipes.Prelude.mapM f

example2' = each xs >-> Pipes.Prelude.sequence

将上述示例与Either一起使用,您可以写:

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

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

>>> Pipes.Prelude.any (> 10) xs
Right True