Haskell Tuple在无限列表上的重构,在将Tuple作为参数重构时,与使用let重构时的表现不同。

问题描述 投票:0回答:1

当尝试使用foldr实现dropWhile时,我想到的第一个算法是这样的

dropWhile' :: (a -> Bool) -> [a] -> [a]
dropWhile' pred = fst . foldr (\cur (acc, xs) ->
    if pred cur
    then (acc, cur:xs)
    else (cur:xs, cur:xs)) ([], [])

虽然这个方法可行,但它会导致无限列表的堆栈溢出,而不给出任何值。因为我不确定为什么这个方法不行,所以我只是玩了一下这个函数,直到我想到了这个。

dropWhile' :: (a -> Bool) -> [a] -> [a]
dropWhile' pred = fst . foldr (\cur t -> let (acc, xs) = t in
    if pred cur
    then (acc, cur:xs)
    else (cur:xs, cur:xs)) ([], [])

正如你所看到的,这个函数和第一个函数唯一的不同之处在于,这里我是对元组进行反构 (acc, xs) 而不是直接在函数参数中进行反构造。由于一些奇怪的原因,这段代码在无限列表上工作。如果有人知道为什么会有这样的行为,请告诉我。

list haskell tuples lazy-evaluation destructuring
1个回答
3
投票

粗略的说。

\cur (acc, xs) -> use cur acc xs

强制立即评估第二个参数,然后才评估 use ....在一个 foldr这将在做其他事情之前处理列表的尾部。如果列表是无限的,我们将陷入无限递归。

这同样适用于

\cur t -> case t of (acc, xs) -> use cur acc xs

而是。

\cur t -> use cur (fst t) (snd t)

并不立即强制评价第二个参数。t,因为Haskell是懒惰的。只有当 use 实际上需要它的第二个或第三个参数,即对 t 将会触发。

等价地,我们可以写成

\cur t -> case t of ~(acc, xs) -> use cur acc xs
-- or
\cur ~(acc, xs) -> use cur acc xs

以达到同样的效果,使用懒惰的irrefutable模式来延迟第二个参数的解构。当使用 letwhere,(顶层)模式是隐性的懒惰,所以我们也可以写成

\cur t -> let (acc, xs) = t in use cur acc xs

7
投票

Haskell中的Let表达式是懒惰的。

Let Expressions

(......)模式绑定是懒惰地匹配的;一个隐式的~使得这些模式不可辩驳。

与此相反,lambda 抽象则可将糖化为 case,从而使构造函数模式变得严格。

Curried applications and lambda abstractions.

翻译。以下身份成立:

\ p1 … pn -> e = \ x1 … xn -> case (x1, …, xn) of (p1, …, pn) -> e

© www.soinside.com 2019 - 2024. All rights reserved.