3

考虑程序:

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

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

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

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

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

这些行为都指向优化功能,但是我想知道是否有更详尽的答案(策略)“幕后”。

4

1 回答 1

2

在这两种情况下都对原始[0..10]列表的元素进行了评估。在此l'案例中未评估的是应用于(+1)列表元素的结果。相反,如果我们严格映射函数,会发生以下情况:

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 提示中没有单态限制不会导致与从文件加载代码时得到的结果不同。)

值得注意的是enumFromTofor Integer(使用范围归结为),base实现,评估元素以便知道何时停止生成它们。也就是说,它并不是length强制列表元素,正如我们希望通过查看它的定义

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''' = [_,_,_]
于 2020-04-13T15:21:20.853 回答