列表连接异常

List concatenation with exception

我有以下表达式:

let x = [1] ++ undefined ++ [3]

我遇到了以下异常:

[1*** Exception: Prelude.undefined
CallStack (from HasCallStack):
  error, called at libraries\base\GHC\Err.hs:79:14 in base:GHC.Err
  undefined, called at <interactive>:39:16 in interactive:Ghci20

与上面的表达式对应:

Prelude> let x = [undefined, undefined, undefined ]
Prelude> length x

在这里,我得到的结果 3 不是异常,因为值在调用之前不会被评估。

为什么前面的表达式引发异常?

如果你写:

let x = [1] ++ undefined ++ [3]

x不计算。它存储为必须计算的表达式。但随后您查询 x。也就是说,你想要show x。现在 为了显示列表,必须评估整个列表(及其元素)

现在 concat 运算符 (++) :: [a] -> [a] -> [a] 定义为:

(++) :: [a] -> [a] -> [a]
(++) (x:xs) ys = x : xs ++ ys
(++) [] ys = ys

例如 [1,2] ++ [3,4]:将导致:

   [1,2] ++ [3,4]
-> 1 : ([2] ++ [3,4])
-> 1 : 2 : ([] ++ [3,4])
-> 1 : 2 : [3,4]

或使用 undefined 时:

   [1,2] ++ undefined
-> 1 : ([2] ++ undefined)
-> 1 : 2 : ([] ++ undefined)
-> 1 : 2 : undefined

请注意,这是 lazily 完成的:只有在需要更多元素时才进一步连接。此外,(++) 关心单个元素:如果一个元素包含计算量大的操作,该操作将推迟到需要计算它时才进行。

所以只要第一个列表还能发出元素,我们就不用管第二个列表。当到达第一个列表的末尾时,我们只需 return 第二个列表。

我们看到这部分有效:解释器打印 <b>[1</b>*** 异常:Prelude.undefined 所以在(++) 运算符的评估,[1] 的第一个元素 被发出 ,评估并打印。但是随后我们到达 undefined 并且 Haskell 无法处理它以打印它(它的元素)并终止。

如果你写 let x = [undefined,undefined,undefined] 而调用 length x,那么 列表的 元素 不会被评估length 定义如下:

length :: [a] -> Int
length [] = 0
length (_:xs) = 1 + length xs

注意函数中的下划线 (_)。 length 不关心单个元素。它会简单地遍历列表,直到它到达空列表 [],并且 return 算在内,所以 3.

如果另一方面你会写

length $ [1] ++ undefined ++ [2,3]

你会遇到同样的问题:

Prelude> length $ [1] ++ undefined ++ [2,3]
*** Exception: Prelude.undefined

因为为了遍历列表,我们必须将 [1]undefined 连接起来。