我是F#和函数式语言的新手.所以这可能是一个愚蠢的问题,或者与F#中的这个递归对象重复?,但我不知道.
这是一个简单的Fibonacci函数:
let rec fib n =
match n with
| 0 -> 1
| 1 -> 1
| _ -> fib (n - 1) + fib (n - 2)
Run Code Online (Sandbox Code Playgroud)
它的签名是int -> int.
它可以改写为:
let rec fib =
fun n ->
match n with
| 0 -> 1
| 1 -> 1
| _ -> fib (n - 1) + fib (n - 2)
Run Code Online (Sandbox Code Playgroud)
它的签名是(int -> int)(在Visual Studio for Mac中).
那么与前一个有什么不同?
如果我再添加一行,如下所示:
let rec fib =
printfn "fib" // <-- this line
fun n ->
match n with
| 0 -> 1
| 1 -> 1
| _ -> fib (n - 1) + fib (n - 2)
Run Code Online (Sandbox Code Playgroud)
IDE给了我一个警告:
警告FS0040:通过使用延迟引用,将在运行时检查对正在定义的对象的这种和其他递归引用的初始化 - 健全性.这是因为您定义了一个或多个递归对象,而不是递归函数.使用'#nowarn"40"'或'--nowarn:40'可以抑制此警告.
这条线如何影响初始化?
"递归对象"是什么意思?我在文档中找不到它.
谢谢你的回复,非常好的解释.
阅读完答案之后,我对Recursive Object有了一些想法.
首先,我在签名上犯了一个错误.上面的前两个代码片段具有相同的签名int -> int; 但最后一个是签名(int -> int)(注意:签名在vscode中具有不同的表示形式,具有Ionide扩展名).
我认为两个签名之间的区别在于,第一个意味着它只是一个函数,另一个意味着它是对函数的引用,即一个对象.
并且每个let rec something没有parameter-list是对象而不是函数,请参阅函数定义,而第二个代码段是异常,可能由编译器优化为函数.
一个例子:
let rec x = (fun () -> x + 1)() // same warning, says `x` is an recursive object
Run Code Online (Sandbox Code Playgroud)
我能想到的唯一一个原因是编译器不够智能,它会抛出一个警告,因为它是一个递归对象,就像警告所示,
这是因为您定义了一个或多个递归对象,而不是递归函数
即使这种模式永远不会有任何问题.
let rec fib =
// do something here, if fib invoked here directly, it's definitely an error, not warning.
fun n ->
match n with
| 0 -> 1
| 1 -> 1
| _ -> fib (n - 1) + fib (n - 2)
Run Code Online (Sandbox Code Playgroud)
你怎么看待这件事?
"递归对象"就像递归函数一样,除了它们是对象.不是功能.
递归函数是一个引用自身的函数,例如:
let rec f x = f (x-1) + 1
Run Code Online (Sandbox Code Playgroud)
递归对象类似,因为它引用自身,除了它不是函数,例如:
let rec x = x + 1
Run Code Online (Sandbox Code Playgroud)
以上实际上不会编译.F#编译器能够正确地确定问题并发出错误:The value 'x' will be evaluated as part of its own definition.显然,这样的定义是荒谬的:为了计算x,你需要知道x.不计算.
但是,让我们看看我们是否可以更聪明.如果我关闭xlambda表达式怎么样?
let rec x = (fun() -> x + 1) ()
Run Code Online (Sandbox Code Playgroud)
在这里,我将x函数包装起来,并立即调用该函数.这会编译,但会有一个警告 - 你得到的相同的警告,一些关于" 在运行时检查初始化 - 健全性 "的事情.
那么让我们去运行时:
> let rec x = (fun() -> x + 1) ()
System.InvalidOperationException: ValueFactory attempted to access the Value property of this instance.
Run Code Online (Sandbox Code Playgroud)
毫不奇怪,我们得到一个错误:事实证明,在这个定义中,你仍然需要知道x才能计算x- 与之相同let rec x = x + 1.
但如果是这样的话,为什么要编译呢?嗯,事实上,通常情况下,无法严格证明x在初始化期间是否会访问自身.编译器足够聪明,可以注意到它可能发生(这就是它发出警告的原因),但不足以证明它肯定会发生.
所以在这种情况下,除了发出警告之外,编译器还会安装一个运行时保护程序,它将检查x在访问它时是否已经初始化.带有这种警卫的编译代码可能如下所示:
let mutable x_initialized = false
let rec x =
let x_temp =
(fun() ->
if not x_initialized then failwith "Not good!"
else x + 1
) ()
x_initialized <- true
x_temp
Run Code Online (Sandbox Code Playgroud)
(实际编译的代码当然看起来不同;使用ILSpy来查看你是否好奇)
在某些特殊情况下,编译器可以证明这种或那种方式.在其他情况下它不能,所以它安装运行时保护:
// Definitely bad => compile-time error
let rec x = x + 1
// Definitely good => no errors, no warnings
let rec x = fun() -> x() + 1
// Might be bad => compile-time warning + runtime guard
let rec x = (fun() -> x+1) ()
// Also might be bad: no way to tell what the `printfn` call will do
let rec x =
printfn "a"
fun() -> x() + 1
Run Code Online (Sandbox Code Playgroud)