这个函数是尾递归的吗?
let rec rec_algo1 step J =
if step = dSs then J
else
let a = Array.init (Array2D.length1 M) (fun i -> minby1J i M J)
let argmin = a|> Array.minBy snd |> fst
rec_algo1 (step+1) (argmin::J)
一般来说,有没有办法正式检查它?
感谢。
答案 0 :(得分:4)
这个函数是尾递归的;我可以通过眼球看出来。
一般来说,并不总是很容易辨别。也许最可靠/实用的事情就是在大输入上检查它(并确保你在'Release'模式下进行编译,因为'Debug'模式会关闭尾调用以获得更好的调试。)
答案 1 :(得分:4)
是的,您可以正式证明函数是尾递归的。每个表达式减少都有一个尾部位置,如果所有递归都在尾部位置,那么该函数是尾递归的。函数可以在一个地方进行尾递归,但不能在另一个地方进行尾递归。
在表达式let pat = exprA in exprB
中,只有exprB
处于尾部位置。也就是说,虽然您可以评估exprA
,但仍需要回过头来评估exprB
并考虑exprA
。对于语言中的每个表达式,都有一个缩减规则,可以告诉您尾部位置的位置。在ExprA; ExprB
再次ExprB
。在if ExprA then ExprB else ExprC
中ExprB
和ExprC
等等。
编译器当然知道这一点。然而,F#中可用的许多表达式以及编译器执行的许多内部优化,例如,在模式匹配编译期间,像seq{}
或async{}
这样的计算表达式可以使得知道哪些表达式处于尾部位置是不明显的。
实际上,通过一些练习,小函数很容易通过查看嵌套表达式并检查函数调用不在尾部位置的槽来确定尾部调用。 (请记住,尾调用可能是另一个函数!)
答案 2 :(得分:4)
你问我们怎么能正式检查这个,所以我会有一个刺。我们首先要定义函数的尾递归意味着什么。表单的递归函数定义
let rec f x_1 ... x_n = e
如果f
内的e
的所有调用都是尾调用,则是尾递归的 - 即。发生在尾部上下文中。 尾部上下文 C
被归纳为具有洞[]
的术语:
C ::= []
| e
| let p = e in C
| e; C
| match e with p_1 -> C | ... | p_n -> C
| if e then C else C
其中e
是F#表达式,x
是变量,p
是模式。我们应该将它扩展为相互递归的函数定义,但我将把它作为一个练习。
现在让我们将此应用于您的示例。函数体中对rec_algo1
的唯一调用就是在这种情况下:
if step = dSs then J
else
let a = Array.init (Array2D.length1 M) (fun i -> minby1J i M J)
let argmin = a|> Array.minBy snd |> fst
[]
由于这是尾部上下文,因此该函数是尾递归的。这就是函数式程序员注意它的方式 - 扫描定义的主体以进行递归调用,然后验证每个函数都出现在尾部上下文中。尾部调用的更直观的定义是除了返回之外没有其他任何操作结果。