3

我遇到了一个看似微不足道的问题:如果函数中的异常是以无点方式编写的,我将无法处理它。

考虑这两个函数:

let divide1 x y =
    try
        x / y
    with
        | :? System.DivideByZeroException -> 42

let divide2 =
    try
        (/)
    with
        | :? System.DivideByZeroException -> fun _ _ -> 42

let x1 = divide1 5 0 // works fine
let x2 = divide2 5 0 // doesn't handle an exception

虽然这两个功能看起来相同,但它们有不同的类型:

val divide1: int -> int -> int
val divide2: (int -> int -> int)

显然,divide2甚至没有尝试处理异常。它只是返回一个运算符。

我该怎么做才能以divide2适当的方式处理异常(除非专门声明其参数)?

4

2 回答 2

7

这是我发现无点样式有问题的原因之一。它使使用标准语言结构try .. with(或标准循环和其他 F# 功能)变得困难,您需要用自定义组合器替换它们。在这种情况下,您可以定义tryWith2在异常处理程序中包装两个参数函数的组合器:

let tryWith2 f h a b = 
  try f a b // Call the function with two arguments
  with e -> 
    // Try running the error handler with the exception
    match h e with 
    | Some g -> g a b // Error handler provided another function
    | _ -> reraise()  // Error was not handled - reraise

然后你可以像这样以无点风格编写函数(错误处理仍然不是无点的,但我不想让它太傻:-))

let divide2 =
  tryWith2 (/) (function
      | :? System.DivideByZeroException -> Some(fun _ _ -> 42)
      | _ -> None)

let x1 = divide2 5 0 // returns 42
let x2 = divide2 5 1 // returns 5

当然,无点样式很有用,即使在 F# 中也是如此。例如,在编写 DSL 时,它是编写声明式规范的好方法(因为原语使用更高级别的抽象来表达某些东西)。在这里,您需要表达一些非常接近普通 F# 代码的内容,我相信,最好将其表达为普通 F# 代码。

于 2012-12-16T01:08:42.770 回答
4

您需要记住的是,在 中divide2,您不是返回 X 除以 Y 的结果,而是返回一个将 X 除以 Y 的函数。 let 绑定的代码将立即执行,因为它没有给出函数语法。让我们看一下具有较长函数语法的两个除法绑定:

let divide1 =
    fun x ->
        fun y ->
            try
                x / y
            with
                | :? System.DivideByZeroException -> 42

let divide2 =
    try
        fun x ->
            fun y ->
                x / y
    with
        | :? System.DivideByZeroException -> fun _ _ -> 42

以这种方式显示时,应该更清楚这两个定义的不同之处。该try块位于完全不同的位置,并在不同的时间点执行。

将异常处理等逻辑添加到现有函数的唯一方法是包装它,或者像在 中所做的那样divide1,或者像 Tomas 所示的那样使用更高阶的函数。

于 2012-12-16T23:47:43.437 回答