GHCi 中的严格列表评估

Strict list evaluation in GHCi

考虑程序:

l = [0..10]
l' = map (+1) [0..10]

运行 使用 GHCi,输入 :sprint l:sprint l' 将显示两个列表都未计算。但是,在 运行 length llength l' 之后,然后再次使用 sprint:

l = [0,1,2,3,4,5,6,7,8,9,10]

l' = [_,_,_,_,_,_,_,_,_,_,_]

我做了类似的实验,并尝试使用 let 将变量绑定到 GHCi 中的列表,但是只有在 l 的情况下(在程序顶层定义如上)是列表总是完全评估。

这些行为都指向优化功能,但是我想知道是否有更详细的答案(策略)'under-the-hood'。

原始 [0..10] 列表的元素在两种情况下都进行了评估。 l' 案例中未计算的是将 (+1) 应用于列表元素的结果。相反,如果我们 map the function strictly:

会发生什么
GHCi> import Control.Monad
GHCi> l'' = (+1) <$!> [0 :: Integer ..10]
GHCi> :sprint l''
l'' = _
GHCi> length l''
11
GHCi> :sprint l''
l'' = [1,2,3,4,5,6,7,8,9,10,11]

(请注意,我专门研究整数文字,因此 GHCi 提示中没有单态限制不会导致与从文件加载代码时得到的结果不同。)

值得注意的是 enumFromTo for Integer(使用范围归结为),as implemented by base, evaluates the elements in order to know when to stop generating them. That's to say it is not length that forces the list elements, as we'd hope from looking at its definition:

length                  :: [a] -> Int
length xs               = lenAcc xs 0

lenAcc          :: [a] -> Int -> Int
lenAcc []     n = n
lenAcc (_:ys) n = lenAcc ys (n+1) 

为了更好地了解 length 在这里的表现,我们可能会尝试使用 replicate 生成的列表重复您的实验(与 length 一样,它不会查看元素)未完全评估的值:

GHCi> n = 2 * (7 :: Integer)  -- let-bindings are lazy.
GHCi> :sprint n
n = _
GHCi> l''' = replicate 3 n
GHCi> :sprint l'''
l''' = _
GHCi> length l'''
3
GHCi> :sprint l'''
l''' = [_,_,_]