Recursion F#递归对象

Recursion F#递归对象,recursion,f#,Recursion,F#,我对F#和函数式语言不熟悉。所以这可能是一个愚蠢的问题,或者与此重复,但我不知道 下面是一个简单的斐波那契函数: 让rec fib n= 匹配 | 0 -> 1 | 1 -> 1 |_u->fib(n-1)+fib(n-2) 它的签名是int->int 它可以重写为: 让rec fib= 乐趣n-> 匹配 | 0 -> 1 | 1 -> 1 |_u->fib(n-1)+fib(n-2) 它的签名是(int->int)(在Visual Studio for Mac中) 那么与前一个有什么不同呢

我对F#和函数式语言不熟悉。所以这可能是一个愚蠢的问题,或者与此重复,但我不知道

下面是一个简单的斐波那契函数:

让rec fib n=
匹配
| 0 -> 1
| 1 -> 1
|_u->fib(n-1)+fib(n-2)
它的签名是
int->int

它可以重写为:

让rec fib=
乐趣n->
匹配
| 0 -> 1
| 1 -> 1
|_u->fib(n-1)+fib(n-2)
它的签名是
(int->int)
(在Visual Studio for Mac中)

那么与前一个有什么不同呢?

如果我再添加一行,如下所示:

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
让rec fib=
打印fn“fib”//
匹配
| 0 -> 1
| 1 -> 1
|_u->fib(n-1)+fib(n-2)
IDE给了我一个警告:

警告FS0040:将通过使用延迟引用在运行时检查对所定义对象的此引用和其他递归引用的初始化可靠性。这是因为您正在定义一个或多个递归对象,而不是递归函数。可以使用“#nowarn“40”或“-nowarn:40”来抑制此警告

此行如何影响初始化?

什么是“递归对象”?我在文档中找不到它。

更新 谢谢你的回复,非常好的解释

在阅读了您的答案之后,我对递归对象有了一些想法

首先,我把签名弄错了。上面的前两个代码段具有相同的签名,
int->int
;但是最后一个有签名
(int->int)
(注意:签名在带有Ionide扩展的vscode中有不同的表示)

我认为这两个签名的区别在于,第一个表示它只是一个函数,另一个表示它是一个函数的引用,也就是一个对象

每个
让rec something
没有
参数列表
是一个对象而不是一个函数,请参见函数,而第二个代码段是一个例外,可能由编译器优化为一个函数

一个例子:

let rec x=(fun()->x+1)(//同样的警告,说'x'是一个递归对象
我能想到的唯一一个原因是编译器不够聪明,它抛出一个警告仅仅是因为它是一个递归对象,就像警告指出的那样

这是因为您正在定义一个或多个递归对象,而不是递归函数

即使这种模式永远不会有任何问题

让rec fib=
//在这里做点什么,如果fib在这里直接调用,这肯定是一个错误,而不是警告。
乐趣n->
匹配
| 0 -> 1
| 1 -> 1
|_u->fib(n-1)+fib(n-2)

您对此有何看法?

最后两个版本之间有一个主要区别。请注意,向第一个版本添加
printfn
调用不会生成警告,并且每次函数递归时都会打印
“fib”

printfn
调用是递归函数体的一部分。但是第三个/最终版本在定义函数时只打印一次“fib”,以后再也不会打印

有什么区别?在第三个版本中,您不仅仅定义递归函数,因为还有其他表达式在lambda上创建闭包,从而生成递归对象。考虑这个版本:

let rec fib3 = 
  let x = 1
  let y = 2
  fun n ->
    match n with
    | 0 -> x
    | 1 -> x
    | _ -> fib3 (n - x) + fib3 (n - y)
fib3
不是一个简单的递归函数;函数捕获
x
y
(对于
printfn
版本也是如此,尽管这只是一个副作用)。此闭包是警告中提到的“递归对象”
x
y
不会在每次递归中重新定义;它们是根级别闭包/递归对象的一部分

从链接的问题/答案中:

因为[编译器]无法保证在初始化引用之前不会访问它

虽然它不适用于您的特定示例,但编译器不可能知道您是否在做无害的事情,或者在
fib3
定义中引用/调用lambda,然后
fib3
具有值/已初始化

“递归对象”与递归函数一样,只是它们是对象。不是功能

递归函数是引用自身的函数,例如:

let rec f x = f (x-1) + 1
递归对象与此类似,因为它引用自身,只是它不是函数,例如:

let rec x = x + 1
上述内容实际上不会编译。F#编译器能够正确确定问题并发出错误:
值“x”将作为其自身定义的一部分进行计算
。显然,这样的定义是荒谬的:为了计算
x
,您需要已经知道
x
。不计算

但让我们看看我们是否能更聪明。如果在lambda表达式中关闭
x
,效果如何

let rec x = (fun() -> x + 1) ()
在这里,我将
x
封装在一个函数中,并立即调用该函数。这会编译,但会有一个警告——与您得到的警告相同,是关于“在运行时检查初始化的合理性”的警告

让我们转到运行时:

> let rec x = (fun() -> x + 1) ()
System.InvalidOperationException: ValueFactory attempted to access the Value property of this instance.
毫不奇怪,我们得到了一个错误:在这个定义中,为了计算
x
,您仍然需要知道
x
——与
let rec x=x+1
相同

但如果是这样,为什么它要编译呢?好吧,通常情况下,不可能严格证明
x
在初始化期间会或不会访问自身。编译器足够聪明,可以注意到它可能发生(这就是它发出警告的原因),b
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
// 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