让我们看看以下函数is_prime
:
let is_prime n =
let rec div_check i = i * i > n || (n mod i <> 0 && div_check (i+1))
in
n >= 2 && div_check 2
因此,如果n mod i <> 0
false ,则会停止。
但如果n mod i <> 0
true ,那么它会以递归方式继续。
我的问题是,如果它继续,OCaml是否优化了编译器,因此只能直接返回div_check(i+1)
,即尾递归?或者它仍会保留true &&
部分并等待div_check
返回?
ps 该函数取自http://www.cs.cornell.edu/Courses/cs3110/2014sp/lectures/2/lec02.html
答案 0 :(得分:4)
答案是肯定的。我已经简化了你的例子,使编译器输出更容易理解:
let rec is_prime n =
let rec div_check i =
i * i > n || (i + i < n && div_check (i+1)) in
div_check n
鉴于此输入,编译器将发出以下程序集(我添加了一些注释):
camlIs_prime__div_check_1010:
.L102:
movq 16(%rbx), %rdi
movq %rax, %rsi
sarq $1, %rsi
movq %rax, %rdx
decq %rdx
imulq %rsi, %rdx ; i * i
incq %rdx
cmpq %rdi, %rdx ; if i * i <= n then return
jle .L101
movq $3, %rax
ret
.L101:
movq 16(%rbx), %rdi
leaq -1(%rax, %rax), %rsi ; i + i
cmpq %rdi, %rsi ; i + i ? n
jge .L100 ; if i + i >= n then return
addq $2, %rax ; else i := i + 1
jmp .L102 ; div_check i
.L100:
movq $1, %rax
ret
但要谨慎,这只适用于vanilla OCaml的短路运营商。
即使是let (&&) = (&&)
break jmp .L102
和call camlIs_prime__div_check_...
这样无害的变更,也会被div_check
取代。
此外,这仅在呼叫位于短路运营商右侧时才有效。左表达式是marked非尾递归。我已对其进行了测试,实际上,将&&
放在-annot
运算符的左侧会发出非尾递归代码。
如果您不确定调用是否为tail,您可以随时向编译器添加*.annot
标志,并查看相应的call (tail)
文件,{{1}注释。在梅林有一些工具支持,但我还没有弄清楚如何正确使用它。请记住,最终的判断仍然是集会输出。