Reed 已经解释了为什么 .NET 异常的行为不同于 OCaml 异常。通常,.NET 异常仅适用于异常 情况,并且是为此目的而设计的。 OCaml 具有更轻量级的模型,因此它们也用于实现一些控制流模式。
举一个具体的例子,在 OCaml 中,你可以使用异常来实现循环中断。例如,假设您有一个函数test 来测试一个数字是否是我们想要的数字。下面遍历从 1 到 100 的数字并返回第一个匹配的数字:
// Simple exception used to return the result
exception Returned of int
try
// Iterate over numbers and throw if we find matching number
for n in 0 .. 100 do
printfn "Testing: %d" n
if test n then raise (Returned n)
-1 // Return -1 if not found
with Returned r -> r // Return the result here
要毫无例外地实现这一点,您有两种选择。您可以编写一个具有相同行为的递归函数 - 如果您调用 find 0(并且它被编译为与在 C# 中的 for 循环中使用 return n 基本相同的 IL 代码):
let rec find n =
printfn "Testing: %d" n
if n > 100 then -1 // Return -1 if not found
elif test n then n // Return the first found result
else find (n + 1) // Continue iterating
使用递归函数的编码可能有点冗长,但您也可以使用 F# 库提供的标准函数。这通常是重写将使用 OCaml 异常进行控制流的代码的最佳方式。在这种情况下,你可以写:
// Find the first value matching the 'test' predicate
let res = seq { 0 .. 100 } |> Seq.tryFind test
// This returns an option type which is 'None' if the value
// was not found and 'Some(x)' if the value was found.
// You can use pattern matching to return '-1' in the default case:
match res with
| None -> -1
| Some n -> n
如果您不熟悉选项类型,请查看一些介绍性材料。 F# wikibook has a good tutorial 和 MSDN documentation has useful examples 也是。
使用Seq 模块中的适当函数通常会使代码更短,因此更可取。它的效率可能比直接使用递归略低,但在大多数情况下,您不必担心这一点。
编辑:我对实际表现很好奇。如果输入是延迟生成的序列seq { 1 .. 100 } 而不是列表[ 1 .. 100 ](因为列表分配的成本),则使用Seq.tryFind 的版本更有效。通过这些更改以及返回第 25 个元素的 test 函数,在我的机器上运行代码 100000 次所需的时间是:
exceptions 2.400sec
recursion 0.013sec
Seq.tryFind 0.240sec
这是非常简单的示例,所以我认为使用Seq 的解决方案通常不会比使用递归编写的等效代码慢10 倍。速度变慢可能是由于分配了额外的数据结构(表示序列的对象、闭包……)以及额外的间接性(代码需要大量的虚拟方法调用,而不仅仅是简单的数字操作和跳转)。但是,异常的成本更高,并且不会以任何方式使代码更短或更易读……