【问题标题】:How to set strictness in list comprehension?如何在列表理解中设置严格性?
【发布时间】:2011-06-26 19:31:24
【问题描述】:

我有点卡住如何重写以下严格评估的列表理解以使用 seq 而不是 bang 模式:

zipWith' f l1 l2 = [ f e1 e2 | (!e1, !e2) <- zip l1 l2 ]

有什么想法吗?

我试过了

zipWith' f l1 l2 = [ e1 `seq` e2 `seq` f e1 e2 | (e1, e2) <- zip l1 l2 ]

但不幸的是,这并不强制对 WHNF 进行评估。

【问题讨论】:

    标签: haskell lazy-evaluation strictness


    【解决方案1】:

    您可以机械地将 bang 模式转换为对 seq 的调用 关注the GHC manual

    这个:

    zipWith' f l1 l2 = [ f e1 e2 | (!e1, !e2) <- zip l1 l2 ]
    

    变得太懒了:

    zipWith' f l1 l2 =
        [ f e1 e2
        | e <- zip l1 l2
        , let t = case e of (x,y) -> x `seq` y `seq` (x,y)
        , let e1 = fst t
        , let e2 = snd t
        ]
    

    哪个更简洁写成(太懒了):

    zipWith' f l1 l2 =
        [ f e1 e2
        | e <- zip l1 l2
        , let (e1,e2) = case e of (x,y) -> x `seq` y `seq` (x,y)
        ]
    

    虽然我会写成(错了,太懒了)

    zipWith' f l1 l2 = zipWith (\x y -> uncurry f (k x y)) l1 l2
        where
            k x y = x `seq` y `seq` (x, y)
    

    您还可以将严格性提示移至数据结构:

    data P = P !Integer !Integer
    
    zipWith' f l1 l2 = [ f e1 e2 | P e1 e2 <- zipWith P l1 l2 ]
    

    或如:

    zipWith' f l1 l2 = [ f e1 e2 | (e1, e2) <- zipWith k l1 l2 ]
        where
            k x y = x `seq` y `seq` (x,y)
    

    【讨论】:

    • 不幸的是,上述版本的 zipWith' 函数看起来都不严格。如果在表达式中运行! (10^6) where fibs = 0:1:zipWith' (+) fibs (tail fibs) 中止并出现“堆栈空间溢出”错误。带有爆炸模式的原始版本在没有堆栈溢出的情况下完成。此外,最后一个示例甚至无法编译:无法匹配预期类型Int' with actual type (t0, t1)' 预期类型:(t0, t1) -> (t0, t1) -> Int 实际类型:(t0, t1) -> (t0, t1) -> (t0, t1) 在zipWith'', namely (+)'的第一个参数中
    • 我已经添加了一些其他的例子,虽然我很惊讶翻译的例子太懒了,正如你所指出的。如果有人想在这里经历严格,请这样做。
    • 谢谢唐,最后一个示例是最接近我的问题的答案,尽管我希望代码更简洁。知道为什么通过直接翻译 bang-patterns 定义得到惰性版本吗? GHC 手册中是否有一些错误或只是过时了?
    • 如果没有其他人提供更好的解决方案,我将接受唐的回答。仍然很好奇为什么 exactly 没有像 GHC 手册中描述的那样使用 case 表达式翻译 bang 模式??
    【解决方案2】:

    您希望严格的zipWith 做的基本事情是在强制使用 cons 单元格时评估列表的元素。您的功能不会那样做。试试看

    main = print $ length $ zipWith' undefined [1..10] [1..100]
    

    当您使用 (+) 使其起作用时,严格分析是侥幸。

    你想要的功能是这样的:

    zipW f (x:xs) (y:ys) = let z = f x y in seq z (z : zipW f xs ys)
    zipW _ _ _ = []
    

    您不能将 cons 单元格的生成与值的生成解耦,因为前者应该强制后者。

    【讨论】:

    • 虽然难以预料,但在列表构造中应用预先严格性的需求听起来合乎逻辑。但最初的问题仍未得到解答:列表理解是否可能? IE。有没有比我的 zipWith' f l1 l2 = [ f e1 e2 | 更简洁的方法? (e1,e2) seq x : seqLst xs) seqLst _ = []
    【解决方案3】:

    解释原始发帖人的例子和唐的一些值得注意的行为。使用 (>>=) 作为简洁的 concatMap,原始示例转换为:

    zip l1 l2 >>= \(!e1, !e2) -> [f e1 e2]
    

    第二个例子是:

    zip l1 l2 >>= \(e1, e2) -> [e1 `seq` e2 `seq` f e1 e2]
    

    但脱糖的第一个版本实际上进一步脱糖的是:

    zip l1 l2 >>= \(e1, e2) -> e1 `seq` e2 `seq` [f e1 e2]
    

    当 concatMap 中的 concat 将这些单例列表变平时,e1 和 e2 会被强制执行,这提供了“我前进时的力量”行为。

    虽然这不是人们通常认为的“严格” zipWith,但它适用于 fibs 示例并不是侥幸。

    【讨论】:

    • 你是对的,这并不是侥幸,因为zipWith'fibs 作为参数。它只会强制元素比真正的条带zipWith 晚一个“步骤”。这是这个例子的侥幸。 :)
    猜你喜欢
    • 2014-12-27
    • 2023-02-24
    • 2011-07-30
    • 2017-10-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-10-06
    • 1970-01-01
    相关资源
    最近更新 更多