当尝试使用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)
而不是直接在函数参数中进行反构造。由于一些奇怪的原因,这段代码在无限列表上工作。如果有人知道为什么会有这样的行为,请告诉我。
粗略的说。
\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模式来延迟第二个参数的解构。当使用 let
和 where
,(顶层)模式是隐性的懒惰,所以我们也可以写成
\cur t -> let (acc, xs) = t in use cur acc xs
Haskell中的Let表达式是懒惰的。
Let Expressions
(......)模式绑定是懒惰地匹配的;一个隐式的~使得这些模式不可辩驳。
与此相反,lambda 抽象则可将糖化为 case
,从而使构造函数模式变得严格。
Curried applications and lambda abstractions.
翻译。以下身份成立:
\ p1 … pn -> e = \ x1 … xn -> case (x1, …, xn) of (p1, …, pn) -> e