Haskell 懒惰和 seq
Haskell laziness and seq
我正在努力理解 Haskell 中的懒惰和 seq
:
在 (1) 中,在基本情况下的打印需要 v
之前,不会对 v
求值是否正确?
在 (2) 中,在每次递归调用之前评估 v'
是否正确,因此在基本情况下不需要评估 v
?如果没有,我该如何执行这个严格的评估?
有什么分析工具可以让我自己确认这两点吗?
main = do
f [1, 2, 3, 4] 0
f' [1, 2, 3, 4] 0
g x = 42 * x -- this could be whatever
-- 1. lazy
f [] v = print v -- base case
f (x:xs) v = f xs v'
where v' = v + g x
-- 2. strict
f' [] v = print v -- base case
f' (x:xs) v = seq v' f' xs v'
where v' = v + g x
我知道 foldl
和 foldl'
在这些情况下会做我想做的事。我更有兴趣了解这是如何实现的。
是的,你是对的。
在操作上,在情况 1 中,变量 v
绑定到一个 thunk,一个未评估的表达式,它变得越来越大,直到 print
强制其评估。内存占用不是恒定的。
在情况 2 中,变量 v
总是绑定到一个评估的数字。递归以常量 space.
运行
顺便说一句,seq v' f' xs v'
写成
是惯用的
v' `seq` f' xs v'
(这是等价的,因为应用程序总是严格执行函数)或者,利用 $!
f' xs $! v'
另一个常见的选择是使用刘海图案而忘记 seq
f' [] !v = print v -- base case
f' (x:xs) !v = f' xs v'
where v' = v + g x
刘海!
确保v
立即被要求,所以即使它是一个thunk,它也会在继续之前被评估。这也确保了恒定的内存占用。
作为严格的分析工具,您可以尝试 Debug.Trace.trace
,它会在需要某些 thunk 时打印调试消息。这不是用于一般输出,但用于一般调试是可以的。
我正在努力理解 Haskell 中的懒惰和 seq
:
在 (1) 中,在基本情况下的打印需要
v
之前,不会对v
求值是否正确?在 (2) 中,在每次递归调用之前评估
v'
是否正确,因此在基本情况下不需要评估v
?如果没有,我该如何执行这个严格的评估?有什么分析工具可以让我自己确认这两点吗?
main = do
f [1, 2, 3, 4] 0
f' [1, 2, 3, 4] 0
g x = 42 * x -- this could be whatever
-- 1. lazy
f [] v = print v -- base case
f (x:xs) v = f xs v'
where v' = v + g x
-- 2. strict
f' [] v = print v -- base case
f' (x:xs) v = seq v' f' xs v'
where v' = v + g x
我知道 foldl
和 foldl'
在这些情况下会做我想做的事。我更有兴趣了解这是如何实现的。
是的,你是对的。
在操作上,在情况 1 中,变量 v
绑定到一个 thunk,一个未评估的表达式,它变得越来越大,直到 print
强制其评估。内存占用不是恒定的。
在情况 2 中,变量 v
总是绑定到一个评估的数字。递归以常量 space.
顺便说一句,seq v' f' xs v'
写成
v' `seq` f' xs v'
(这是等价的,因为应用程序总是严格执行函数)或者,利用 $!
f' xs $! v'
另一个常见的选择是使用刘海图案而忘记 seq
f' [] !v = print v -- base case
f' (x:xs) !v = f' xs v'
where v' = v + g x
刘海!
确保v
立即被要求,所以即使它是一个thunk,它也会在继续之前被评估。这也确保了恒定的内存占用。
作为严格的分析工具,您可以尝试 Debug.Trace.trace
,它会在需要某些 thunk 时打印调试消息。这不是用于一般输出,但用于一般调试是可以的。