F# F中的奇怪行为#
我尝试在F#中定义自己的逻辑隐含运算符,如下所示F# F中的奇怪行为#,f#,F#,我尝试在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 > >fal
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
>
>false-->((2/0)=1);;
System.DivideByZeroException:尝试除以零。在$FSI_0015.main@()
由于错误而停止
>
它不应该评估结果,但它是
有人能看出这里出了什么问题吗?我在VS 2012中运行F#当您编写a-->b
时,真正发生的是调用一个名为-->
的函数,该函数有两个参数,a
和b
。运算符语法只是一些语法糖
在调用函数之前,运行时必须计算该函数的所有参数。因此,在调用-->
之前,它首先计算false
,然后计算(2/0)=1
。计算最后一个表达式时,它会引发异常。你的函数永远不会被调用
在其他一些语言中,例如Haskell,您有延迟计算。也就是说,只有在函数内部实际访问函数的参数时,才会对其求值。您可以通过不传递值,而是传递计算结果为该值的函数或调用时的函数来模拟该情况
请注意,要在F#(thunks)中实现此类功能,必须对函数进行轻微修改:它必须调用thunk以获取其值,就像John Palmer给出的示例:
let --> p q = (not p) || q()
let thunk = (fun _ -> ((2/0)=1))
false --> thunk
注意隐式运算符定义中的函数调用
q()
。如果你不在第二个论点上重击一下,它就不会再起作用了 必须在将RHS传递给函数之前对其进行求值,因此被零除的时间很早,因此这样的保护将不起作用
您可以像这样传递函数
let --> p q = (not p) || q()
false --> (fun _ -> ((2/0)=1))
您还可以使用
lazy
根据需要计算第二个参数:
let (-->) p (q: Lazy<bool>) = (not p) || q.Force()
F#有严格的评估策略。这意味着参数在传递给函数之前要进行求值。因此,2/0=1
在传递给-->
函数之前进行求值,因此|
的短路不会影响2/0=1
的求值,因为这是在|
之前进行求值的
您需要将函数(-->
运算符)转换为按名称而不是按值获取其参数。实际上,这意味着需要()->'T
或Lazy…
,但我认为您可以通过定义一些常量来简化样板文件:
let True, False = (fun () -> true), (fun () -> false)
为了进一步简化为每个参数创建lambda的样板,您可以尝试使用代码引用(以及类似于对其求值的库):
let(-->)pq=(not(eval p))| |(eval q)
-->
@MisterMetaphor——这与评估顺序无关;这是关于渴望与懒惰的评估,这与顺序是正交的。@OnorioCatenacci,你是对的,我想我指的是评估策略。谢谢
let (-->) p q = (not <| p()) || q()
> (fun () -> false) --> (fun () -> 2/0=1);;
true
let (-->) (p : Lazy<_>) (q : Lazy<_>) = (not <| p.Force()) || q.Force()
> lazy false --> lazy (2 / 0 = 1)
true
let True, False = (fun () -> true), (fun () -> false)
let (-->) p q = (not (eval p)) || (eval q)
<@ false @> --> <@ 2 / 0 = 1 @>