如何判断OCaml是否将特定函数识别为尾递归?特别是,我想知道OCaml编译器是否识别Short-circuited operators and tail recursion
感谢Jeffrey在下面的回答,我用简单的函数
尝试了这个let rec check_all l =
match l with
| [] -> true
| hd :: tl ->
hd && check_all tl
实际上,它确实优化为:
camlTest__check_all_1008:
.cfi_startproc
.L102:
cmpl $1, %eax
je .L100
movl (%eax), %ebx
cmpl $1, %ebx
je .L101
movl 4(%eax), %eax
jmp .L102
.align 16
.L101:
movl $1, %eax
ret
答案 0 :(得分:9)
从OCaml 4.03开始,尽管Changes文件中存在拼写错误,但您可以在函数应用程序中使用@tailcall
,如果不是这样,编译器将发出警告。
(f [@tailcall]) x y
会在f x y
不是尾声调用时发出警告
示例:
$ cat tailrec.ml
let rec f a x = if x <= 1 then a else (f [@tailcall]) (a * x) (x - 1)
let rec g x = if x <= 1 then 1 else x * (g [@tailcall]) (x - 1)
$ ocaml tailrec.ml
File "tailrec.ml", line 3, characters 40-63:
Warning 51: expected tailcall
答案 1 :(得分:8)
许多其他人比我更关注OCaml内部,但对于简单的函数,很容易在生成的ocamlopt汇编代码中看到尾递归:
$ cat tailrec.ml
let rec f a x = if x <= 1 then a else f (a * x) (x - 1)
let rec g x = if x <= 1 then 1 else x * g (x - 1)
$ ocamlopt -c -S tailrec.ml
如果您忽略了大量额外输出,则会看到f
:
_camlTailrec__f_1008:
.cfi_startproc
.L101:
cmpq $3, %rbx
jg .L100
ret
.align 2
.L100:
movq %rbx, %rdi
addq $-2, %rdi
sarq $1, %rbx
decq %rax
imulq %rbx, %rax
incq %rax
movq %rdi, %rbx
jmp .L101
.cfi_endproc
编译器已将递归调用更改为循环(即函数是尾递归)。
以下是g
的内容:
.cfi_startproc
subq $8, %rsp
.cfi_adjust_cfa_offset 8
.L103:
cmpq $3, %rax
jg .L102
movq $3, %rax
addq $8, %rsp
.cfi_adjust_cfa_offset -8
ret
.cfi_adjust_cfa_offset 8
.align 2
.L102:
movq %rax, 0(%rsp)
addq $-2, %rax
call _camlTailrec__g_1011
.L104:
movq %rax, %rbx
sarq $1, %rbx
movq 0(%rsp), %rax
decq %rax
imulq %rbx, %rax
incq %rax
addq $8, %rsp
.cfi_adjust_cfa_offset -8
ret
.cfi_adjust_cfa_offset 8
.cfi_endproc
递归由实际的递归调用处理(不是尾递归)。
正如我所说,如果你比我更了解OCaml中间形式,可能有更好的方法来解决这个问题。
答案 2 :(得分:1)
我想知道,为什么没有人告诉过古老的-annot
选项,它会为所有调用转储注释。虽然使用装配是最确定的方法,但并不是每个人都擅长阅读装配。但是,如果没有它,那么它甚至可以自动化。例如,假设您的代码位于test.ml
文件中,我们可以使用以下单行自动检查所有调用是否处于尾部位置:
ocamlc -annot test.ml && if grep -A1 call test.annot | grep stack; then echo "non tailrecursive"; exit 1; fi
ocaml -annot test.ml
将为文件编译一个create test.annot
文件,该文件将包含每个表达式的注释。 grep -A1 call test.annot
将提取所有调用注释,并查看其内容。如果至少有一个堆栈调用,grep stack
将返回true。
实际上甚至还有一个emacs补充,您可以在the ocaml存储库中找到它,它将从annot
文件中提取此信息。例如,有一个caml-types-show-call
函数,它将显示一个在该点指定的函数调用。但是,此函数目前有一个错误(看起来不再支持),要修复它,您需要将以下补丁应用于它:
--- a/emacs/caml-types.el
+++ b/emacs/caml-types.el
@@ -221,7 +221,7 @@ See `caml-types-location-re' for annotation file format."
(right (caml-types-get-pos target-buf (elt node 1)))
(kind (cdr (assoc "call" (elt node 2)))))
(move-overlay caml-types-expr-ovl left right target-buf)
- (caml-types-feedback kind)))))
+ (caml-types-feedback "call: %s" kind)))))
(if (and (= arg 4)
(not (window-live-p (get-buffer-window caml-types-buffer))))
(display-buffer caml-types-buffer))