【问题标题】:Cont Monad breaks laziness in HaskellCont Monad 打破 Haskell 的懒惰
【发布时间】:2014-03-23 19:43:43
【问题描述】:

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

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

代码如下:

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

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

【问题讨论】:

标签: haskell monads continuations


【解决方案1】:

原因是虽然sequence someList的头部元素的只依赖于someList的第一个元素,但sequence someList效果通常可以依赖于someList 的所有效果(它适用于大多数单子)。因此,如果我们要评估头部元素,我们仍然需要评估所有的效果。

例如,如果我们有一个Maybe 值的列表,那么只有当someList 的所有元素都是Just 时,sequence 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。因此,为了查看Contsequence someListhead 元素是什么,我们需要再次计算它们。

【讨论】:

    【解决方案2】:

    这不是Cont monad 的缺陷,而是sequenceEither 可以得到类似的结果,例如:

    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
    

    【讨论】:

    • 我认为将 mapM 称为一般的反模式有点苛刻。我会说只有当列表的长度不受限制时(例如从用户输入派生)才不好。但是有例如在最多 100 个元素的列表上调用 mapM 并没有太大问题。
    • mapM 函数根本不是反模式,只不过是 effect 是反模式。
    • 使用具有 O(N^2) 空间和时间复杂度且在存在流式 O(N) 解决方案时不会流式传输的东西在我看来是一种反模式。另外,请注意,OP 专门用他们自己的话将这种行为描述为有问题的,因此此声明与此问题的上下文相关。
    • 感谢您提供有关 Pipes 的信息,看起来很棒。
    猜你喜欢
    • 1970-01-01
    • 2014-03-23
    • 2015-09-06
    • 1970-01-01
    • 2015-09-06
    • 1970-01-01
    • 1970-01-01
    • 2016-12-14
    • 2011-03-20
    相关资源
    最近更新 更多