2

我尝试在 F# 中定义自己的逻辑暗示运算符,如下所示

let (-->) p q = (not p) || q

但是当我真正尝试它时,它似乎没有实现短路或

> false --> ((2/0)=1);;
System.DivideByZeroException: Attempted to divide by zero.at <StartupCode$FSI_0015>.$FSI_0015.main@()
Stopped due to error
> 

它不应该评估结果,但它是

谁能看到这里有什么问题?我在 VS 2012 中运行 F#

4

4 回答 4

10

当您编写. a --> b_ 运算符语法只是一些语法糖。-->ab

在调用函数之前,运行时必须评估该函数的所有参数。因此,在调用 之前-->,它首先评估false然后(2/0)=1。在评估最后一个表达式时,它会引发异常。你的暗示函数永远不会被调用!

在其他一些语言中,比如 Haskell,你有惰性求值。也就是说,函数的参数只有在函数内部被实际访问时才会被评估。您可以通过不传递值来模拟它,而是传递一个计算为该值的函数,或者在调用它时传递一个thunk

请注意,要在 F#(thunk)中实现这种功能,必须稍微修改函数:它必须调用thunk 来获取其值,就像 John Palmer 给出的示例一样:

let --> p q = (not p) || q()
let thunk = (fun _ -> ((2/0)=1))
false --> thunk

注意隐含q()运算符定义中的函数调用。如果您不考虑第二个参数,它将不再起作用!

于 2013-07-25T21:50:55.937 回答
6

您还可以使用lazy按需评估第二个参数:

let (-->) p (q: Lazy<bool>) = (not p) || q.Force()

您可以将其称为:

false --> (lazy (2/0=1))
于 2013-07-25T22:01:26.513 回答
3

RHS 的评估必须在它被传递给函数之前发生,所以除以零很早就发生了,所以这样的保护不起作用。

你可以像这样传递一个函数

let --> p q = (not p) || q()

false --> (fun _ -> ((2/0)=1))
于 2013-07-25T21:51:08.280 回答
2

F# 有严格的评估策略。这意味着参数在传递给函数之前会被评估。因此,2/0=1在将其传递给您的-->函数之前对其进行评估,因此短路||不会影响 的评估2/0=1,因为它是在之前评估的||

您需要将函数(-->运算符)转换为按名称而不是按值获取参数。实际上,这意味着使其采用() -> 'Tor Lazy<'T>

let (-->) p q = (not <| p()) || q()

> (fun () -> false) --> (fun () -> 2/0=1);;
true

或者,或者,使用内置lazy构造。这样,您可以使用更简洁的语法获得相同的结果(前提是您的计算不依赖于副作用):

let (-->) (p : Lazy<_>) (q : Lazy<_>) = (not <| p.Force()) || q.Force()

> lazy false --> lazy (2 / 0 = 1)
true

它正在工作,但看起来不太方便。不幸的是,我认为没有简单的方法可以摆脱fun () -> ...,但我认为您可以通过定义一些常量来减少样板:

let True, False = (fun () -> true), (fun () -> false)

为了进一步减少为每个参数创建 lambda 的样板,您可以尝试使用代码引用(以及像Unquote这样的库来评估它们):

let (-->) p q = (not (eval p)) || (eval q)

<@ false @> --> <@ 2 / 0 = 1 @>
于 2013-07-26T06:35:57.783 回答