2
    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

我不太明白这里的意思。

4

2 回答 2

1

这基本上只是一个玩具示例,用于说明您可能想要使用异常的原因,因此无需深入研究。

要理解这一点,您只需要意识到列表占用空间。因此,一个列表的两个副本将占用单个列表空间的两倍。但是在像 OCaml 这样的函数式语言中,列表是不可变的。由于无法修改列表,因此列表和列表副本之间没有可察觉的差异。因此,如果您在两个地方使用相同的列表而不是实际复制列表,那么您可以在不以任何方式改变程序含义的情况下节省空间。

当被要求删除不存在的元素时,您显示的代码会执行此操作。结果将与输入相同,并且代码确保在这种情况下结果与输入相同(不是副本)。

请注意,节省空间来自避免复制,而不是来自异常本身。但是当列表不太长时,这段代码很好而且很紧凑。

我希望这有帮助。

(边节点:在 OCaml 中,您实际上可以使用==运算符检测两个列表之间的差异。这就是人们经常避免使用此运算符的原因;它损害了语言的功能纯度。您一定要小心它。)

于 2013-10-08T15:08:38.487 回答
0

表达式y :: remove x l是复制发生的地方。那是因为::是列表元素的内置 [infix operator] 构造函数。

在该remove函数的原始版本中,::运算符用于构造一个新列表,该列表具有原始列表的所有元素,除了第一个等效于x参数的元素。如果原始列表没有这样的元素,那么它仍然会创建新列表,并且它将是与原始列表等效的副本。

remove函数的“改进”版本调用另一个函数,当它到达原始列表的末尾而没有删除元素时remove_inner会引发异常。Unchanged如果外部函数捕获到异常remove,则返回原始列表。Unchanged

于 2013-10-08T21:28:15.020 回答