let rec remove x = function
y :: l when x = y -> l
|y :: l (* x <> y *) -> y :: remove x l
|[] -> []
这本书说这个函数有一个问题:当找不到元素时,整个列表被不必要地复制。因此,给出以下改进版本。
exception Unchanged
let rec remove_inner x = function
y :: l when x = y ->
l
|y :: l ->
y :: remove_inner x l
|[] ->
raise Unchanged
let remove x l =
try remove_inner x l with
Unchanged ->
l
我不太明白这一点。
答案 0 :(得分:1)
这基本上只是一个玩具示例,以说明您可能想要使用异常的原因,因此无需深入了解它。
要理解这一点,您只需要意识到列表会占用空间。因此,列表的两个副本将占用单个列表的两倍空间。但是在像OCaml这样的函数式语言中,列表是不可变的。由于无法修改列表,因此列表与列表副本之间没有可检测的差异。因此,如果您在两个地方使用相同的列表而不是实际复制列表,则可以在不改变程序含义的情况下节省空间。
当您被要求删除不存在的元素时,您显示的代码会执行此操作。结果将与输入相同,并且代码确保结果与输入(不是副本)相同。
请注意,节省空间来自避免复制,而不是来自异常本身。但是当列表不太长时,这段代码很好而且很紧凑。
我希望这会有所帮助。
(侧节点:在OCaml中,您可以使用==
运算符实际检测两个列表之间的差异。这就是人们经常避免使用此运算符的原因;它会损害语言的功能纯度。您肯定想要要小心。)
答案 1 :(得分:0)
表达式y :: remove x l
是复制的位置。那是因为::
是列表元素的内置[中缀运算符]构造函数。
在remove
函数的幼稚版本中,::
运算符用于构造一个新列表,其中包含原始列表的所有元素,但第一个元素等同于{{1}参数。如果原始列表没有这样的元素,那么它仍然会创建新列表,它将是一个等同于原始列表的副本。
x
函数的“改进”版本调用另一个函数remove
,当它到达原始列表的末尾而不删除元素时会引发remove_inner
异常。如果外部Unchanged
函数捕获remove
异常,则返回原始列表。