无点函数中的异常处理

时间:2012-12-15 19:45:53

标签: f# pointfree

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

考虑这两个功能:

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以正确的方式处理异常(除非明确声明其参数)?

2 个答案:

答案 0 :(得分: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#代码表示。

答案 1 :(得分: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所显示的那样包含更高阶函数。

相关问题