【问题标题】:Strict list evaluation in GHCiGHCi 中的严格列表评估
【发布时间】:2020-04-13 13:41:15
【问题描述】:

考虑程序:

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(如上在程序顶层定义)的情况下,列表总是被完全评估。

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

【问题讨论】:

  • 嗯,元素永远不会被评估,因为我们不需要它们,也不做任何比较,与元素的相等性检查。这与列表没有太大关系,它会发生在所有数据结构中。
  • 旁注:一个潜在的混淆来源可以通过使列表单态来消除 ([0 :: Integer ..10])。这样,从文件加载定义应该与在 GHCi 提示符下输入定义具有相同的效果。
  • 我在这里没有看到实际问题。

标签: haskell evaluation strictness


【解决方案1】:

原始[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 评估元素以便知道何时停止生成它们。也就是说,强制列表元素的不是length,正如我们希望通过查看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''' = [_,_,_]

【讨论】:

  • 我部分不同意“enumFromTo 必须评估元素”。例如,eft a b = take (b-a+1) $ iterate succ a 对我来说似乎是 Int 的正确实现,并且在强制长度后只产生 [1,_,_,_,_,_,_,_,_,_]。一个更人为的例子:“悲观”eft a b = map id (eunmFromTo a b) 也是正确的(IMO)并产生未评估的元素。这很愚蠢,但并非不可能。
  • @chi 你是对的,当然;答案已更正。我可能不应该在不提及enumFromTo @Integer的实际实现的情况下逃避...
最近更新 更多