Haskell 惰性计算和无限列表的引入符号
Haskell lazy evaluation and let-in notation with infinite lists
设 pack
为函数 [a] -> [[a]]
,它接受一个列表并将连续重复的元素分组到子列表中。
这里是 Haskell.
中 pack
的两个实现
pack :: (Eq a) => [a] -> [[a]]
pack x = reverse $ foldl f [] x where
f cks@(ck1:_):rest) x
| x == ck1 = (x:ck):rest
| otherwise [x]:cks
f _ x = [[x]]
pack' (x:xs) = let (first,rest) = span (==x) xs
in (x:first) : pack' rest
pack' [] = []
这些实现有一个关键的语义差异:如果我们将第一个实现应用于无限列表,第一个实现将无法终止,例如[1..]
。 但是第二种实现确实适用于无限列表。例如,head $ pack' [1..]
计算。
我的猜测是 let
in
符号是惰性的,因此 span
(在其 Prelude 定义中使用 let
-in
)仅计算当我们在无限列表上应用 pack'
时,有有限多个表达式。
然而,这对我来说是一个不能令人满意的解释,因为我可以用下面的定义替换reverse
。
reverse' = foldl (\y x0 -> x0:y) []
如果我们这样做,pack
中的每个表达式都会从左向右折叠——所以我希望这对无限列表有效——但它仍然挂起。
问题:为什么 pack'
适用于无限列表而不是 pack
?
foldl :: Foldable f => (b -> a -> b) -> b -> f a -> b
将用于给定函数 f
,基值 z
用于列表 [x<sub>1</sub>, x<sub>2</sub>, …, x<sub>n</sub>]
产生以下结果:
f (f (… (f (f z x1) x2) …) xn-1) xn
如果我们因此想要确定 弱头部范式 (WHNF),我们需要访问列表的最后一个元素。 foldl
的折叠函数 f
的第一个参数可以是惰性的,但我们至少必须使用 x<sub>n[=54 进行函数调用=]
作为参数。这就是为什么 documentation on foldl
says:
Note that to produce the outermost application of the operator the entire input list must be traversed. Like all left-associative folds, foldl
will diverge if given an infinite list.
My guess is the let in notation is lazy, hence span (which uses let-in in its Prelude definition) only evaluates finitely many expressions when we apply pack' on an infinite list.
你是对的,let
、where
子句和所有其他子表达式中的定义都是惰性的。但最终如果你对结果感兴趣,你需要确定WHNF,有时甚至超过WHNF。
它起作用的原因是因为 span :: (a -> Bool) -> [a] -> ([a], [a])
is implemented lazily. Indeed, span
is implemented as [src]:
span :: (a -> Bool) -> [a] -> ([a],[a])
span _ xs@[] = (xs, xs)
span p xs@(x:xs')
| p x = let (ys,zs) = span p xs' in (x:ys,zs)
| otherwise = ([],xs)
因此 不需要 需要知道尾部的 span
看起来如何才能生成一个二元组,其中 x
满足谓词放在第一项,如果 p x
失败,则放在第二项。
这意味着 span
将生成一个二元组,其中第一项将包含满足谓词的所有元素,而第二项是对列表其余部分的惰性引用以进行处理.
设 pack
为函数 [a] -> [[a]]
,它接受一个列表并将连续重复的元素分组到子列表中。
这里是 Haskell.
中pack
的两个实现
pack :: (Eq a) => [a] -> [[a]]
pack x = reverse $ foldl f [] x where
f cks@(ck1:_):rest) x
| x == ck1 = (x:ck):rest
| otherwise [x]:cks
f _ x = [[x]]
pack' (x:xs) = let (first,rest) = span (==x) xs
in (x:first) : pack' rest
pack' [] = []
这些实现有一个关键的语义差异:如果我们将第一个实现应用于无限列表,第一个实现将无法终止,例如[1..]
。 但是第二种实现确实适用于无限列表。例如,head $ pack' [1..]
计算。
我的猜测是 let
in
符号是惰性的,因此 span
(在其 Prelude 定义中使用 let
-in
)仅计算当我们在无限列表上应用 pack'
时,有有限多个表达式。
然而,这对我来说是一个不能令人满意的解释,因为我可以用下面的定义替换reverse
。
reverse' = foldl (\y x0 -> x0:y) []
如果我们这样做,pack
中的每个表达式都会从左向右折叠——所以我希望这对无限列表有效——但它仍然挂起。
问题:为什么 pack'
适用于无限列表而不是 pack
?
foldl :: Foldable f => (b -> a -> b) -> b -> f a -> b
将用于给定函数 f
,基值 z
用于列表 [x<sub>1</sub>, x<sub>2</sub>, …, x<sub>n</sub>]
产生以下结果:
f (f (… (f (f z x1) x2) …) xn-1) xn
如果我们因此想要确定 弱头部范式 (WHNF),我们需要访问列表的最后一个元素。 foldl
的折叠函数 f
的第一个参数可以是惰性的,但我们至少必须使用 x<sub>n[=54 进行函数调用=]
作为参数。这就是为什么 documentation on foldl
says:
Note that to produce the outermost application of the operator the entire input list must be traversed. Like all left-associative folds,
foldl
will diverge if given an infinite list.
My guess is the let in notation is lazy, hence span (which uses let-in in its Prelude definition) only evaluates finitely many expressions when we apply pack' on an infinite list.
你是对的,let
、where
子句和所有其他子表达式中的定义都是惰性的。但最终如果你对结果感兴趣,你需要确定WHNF,有时甚至超过WHNF。
它起作用的原因是因为 span :: (a -> Bool) -> [a] -> ([a], [a])
is implemented lazily. Indeed, span
is implemented as [src]:
span :: (a -> Bool) -> [a] -> ([a],[a]) span _ xs@[] = (xs, xs) span p xs@(x:xs') | p x = let (ys,zs) = span p xs' in (x:ys,zs) | otherwise = ([],xs)
因此 不需要 需要知道尾部的 span
看起来如何才能生成一个二元组,其中 x
满足谓词放在第一项,如果 p x
失败,则放在第二项。
这意味着 span
将生成一个二元组,其中第一项将包含满足谓词的所有元素,而第二项是对列表其余部分的惰性引用以进行处理.