我遇到了一个看似微不足道的问题:如果函数以无点方式编写,我就无法处理函数中的异常。
考虑这两个功能:
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
以正确的方式处理异常(除非明确声明其参数)?
答案 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所显示的那样包含更高阶函数。