正如 John 所建议的,索引空列表可能会引发异常而不是返回 0。这使得 nth 适用于任何类型的列表,而不仅仅是 int lists 的子集,其中 0 可以合理地被认为是“不结果”。似乎该函数缺少递归,无法用于超过 0 的任何索引。这是一个可以使用的模板:
fun nth ([], _) = raise Empty
| nth (x::_, 0) = x
| nth (_::xs, n) = ...
这里添加了一个例外,并且在函数的每种情况下都不会使用的变量已被伪变量_ 屏蔽掉。您可能还需要更多信息的错误消息。
fun nth ([], n) = raise Fail "Failed to find the appropriate index!"
| nth (x::_, 0) = x
| nth (_::xs, n) = ...
nth 的“更安全”版本具有 'a list * int -> 'a option 类型,即对于 nth (xs, i),如果 xs 具有 ith 元素 x,则返回 SOME x,如果没有't,它返回NONE:
fun nth_safe ([], _) = NONE
| nth_safe (x::_, 0) = SOME x
| nth_safe (_::xs, n) = ...
它“更安全”,因为如果列表不够长,它不会抛出异常。对抗性示例:nth ([0,1,2], 3)
但如果索引为负数,它仍然无法处理。对抗性示例:nth ([0,1,2], ~1)
您可以使用 if n < 0 then ... 在第三个函数体的 ... 中解决该问题,但随后会在每个递归步骤中执行,即使您很可能只需要检查一次。
当您向它传递负索引时,此函数的健壮版本会引发错误。否则,您的函数可能会导致您进行负循环,直到内存不足,因为递归情况(第三种情况)不会收敛到两种基本情况(情况 1 和 2)。对于基于异常的版本,可以这样写:
exception IndexError of int
fun nth (xs, n) =
let fun go ([], _) = raise IndexError n
| go (x::_, 0) = x
| go (_::ys, i) = ...
in if n < 0 then raise IndexError n else go (xs, n)
end
使用错误感知数据类型的健壮版本可能看起来像:
fun nth (xs, n) =
let fun go ([], _) = NONE
| go (x::_, 0) = SOME x
| go (_::ys, i) = ...
in if n < 0 then NONE else go (xs, n)
end
一个健壮的版本使用错误感知数据类型来捕获索引错误,就像带有自定义 IndexError 异常的基于异常的版本一样:
datatype ('a, 'b) either = Left of 'a | Right of 'b
fun nth (xs, n) =
let fun go ([], _) = Left n
| go (x::_, 0) = Right x
| go (_::ys, i) = ...
in if n < 0 then Left n else go (xs, n)
end
val example_1 = nth ([2,3,5], 5) (* gives: Left 5 *)
val example_2 = nth ([2,3,5], ~1) (* gives: Left ~1 *)
val example_3 = nth ([2,3,5], 2) (* gives: Right 5 *)