在F#中将递归函数标记为rec的原因是什么?

Joa*_*nge 15 recursion f# function

我不确定这是否是一个愚蠢的问题,但我正在阅读VS 2010附带的教程,并且有一个这样的函数:

let rec factorial n = if n=0 then 1 else n * factorial (n-1)
Run Code Online (Sandbox Code Playgroud)

这个递归函数用rec关键字标记的原因是什么?

这样编译器可以确保它是递归的,所以可以进行某些优化吗?

如果排除它会怎么样?

Bri*_*ian 28

这可能是有益的:

let Main() =
    let f(x) = 
        printfn "original f: %d" x
    let f(x) =
    //let rec f(x) =
        printfn "entered new f: %d" x
        if x > 0 then
            f(x-1)
        else
            printfn "done"
    f(3)
Main()
Run Code Online (Sandbox Code Playgroud)

那打印

entered new f: 3
original f: 2
Run Code Online (Sandbox Code Playgroud)

现在,如果我们注释掉let并取消注释let rec,则打印出来

entered new f: 3
entered new f: 2
entered new f: 1
entered new f: 0
done
Run Code Online (Sandbox Code Playgroud)

所以从这个角度来看,它只是名称绑定; let rec将标识符立即放入作用域(在此示例中,遮蔽前一个f),而let仅在定义其正文后将标识符放在作用域中.

规则的动机源于与类型推断的交互.

  • +1,但"规则的动机源于与类型推断的相互作用"可以做一些澄清. (3认同)
  • 这是关于绑定,阴影和范围的一个非常有教育意义的例子:-). (2认同)

Rus*_*Cam 11

据Chris Smith说(在F#团队工作) -

通知类型推断系统允许该函数用作类型推断过程的一部分.rec允许您在类型推断系统确定函数类型之前调用该函数

  • 如果没有`rec`,你还必须事先定义函数本身.但是你必须首先定义函数,因为它在自己的范围内不可用.但首先,您必须定义该功能.定义功能后.一旦定义了函数.这将需要定义功能.*无限期.* (4认同)
  • 通过它,它允许您在定义的同时使用它 (2认同)

Gor*_*aci 8

根据MSDN,它只是一种合成必需品:

递归函数,调用自身的函数,在F#语言中明确标识.这使得在函数范围内定义的标识符可用.

http://msdn.microsoft.com/en-us/library/dd233232.aspx


Jon*_*rop 6

这个递归函数用rec关键字标记的原因是什么?

告诉编译器函数体内函数名的任何使用都是递归地引用它而不是先前定义的同名值.

这样编译器可以确保它是递归的,所以可以进行某些优化吗?

没有.

如果排除它会怎么样?

您失去了定义的函数在其函数体中引用自身的能力,并且能够引用先前定义的同名值.


Chu*_*uck 5

这是必要的,以便函数可以递归.非rec函数只知道绑定在它定义的地方,而不是之后(所以它不知道自己).

  • @Joan Venge:绑定基本上是一个变量名及其相关值.在这种情况下,它意味着您不能在其自身内部使用该函数,因为它仅在*let之后绑定到名称*.`let rec`使绑定表达式本身内部绑定的名称*可用. (3认同)