我正在阅读On Lisp中的第2.8节(尾递归)。它有一个尾递归函数的例子:
(defun our-length-tr (lst)
"tail recursive version with accumulator"
(labels ((rec (lst acc)
(if (null lst)
acc
(rec (cdr lst) (1+ acc)))))
(rec lst 0)))
它表示许多 Common Lisp编译器会执行TCO,但您可能需要在文件顶部添加(proclaim '(optimize speed))
。
如何确定我的编译器支持TCO,并且它会将我的函数编译为循环版本而不是递归版本?
答案 0 :(得分:5)
有几种简单的方法可以检查函数是否使用尾递归进行编译。
如果你可以阅读汇编语言,那么可以使用原始函数disassemble
(参见https://www.java.com/en/jre-jdk-cryptoroadmap.html),例如:
* (disassemble 'our-length-tr)
; disassembly for OUR-LENGTH-TR
; Size: 89 bytes. Origin: #x10034F8434
; 34: 498B4C2460 MOV RCX, [R12+96] ; no-arg-parsing entry point
; thread.binding-stack-pointer
; 39: 48894DF8 MOV [RBP-8], RCX
; 3D: 488B4DF0 MOV RCX, [RBP-16]
; 41: 31D2 XOR EDX, EDX
; 43: EB3E JMP L2
; 45: 660F1F840000000000 NOP
; 4E: 6690 NOP
; 50: L0: 4881F917001020 CMP RCX, #x20100017 ; NIL
; 57: 7506 JNE L1
; 59: 488BE5 MOV RSP, RBP
; 5C: F8 CLC
; 5D: 5D POP RBP
; 5E: C3 RET
; 5F: L1: 8D41F9 LEA EAX, [RCX-7]
; 62: A80F TEST AL, 15
; 64: 751F JNE L3
; 66: 488B5901 MOV RBX, [RCX+1]
; 6A: 48895DE8 MOV [RBP-24], RBX
; 6E: BF02000000 MOV EDI, 2
; 73: 41BBF004B021 MOV R11D, #x21B004F0 ; GENERIC-+
; 79: 41FFD3 CALL R11
; 7C: 488B5DE8 MOV RBX, [RBP-24]
; 80: 488BCB MOV RCX, RBX
; 83: L2: EBCB JMP L0
; 85: L3: 0F0B0A BREAK 10 ; error trap
; 88: 2F BYTE #X2F ; OBJECT-NOT-LIST-ERROR
; 89: 08 BYTE #X08 ; RCX
; 8A: 0F0B10 BREAK 16 ; Invalid argument count trap
NIL
(Mac OS X 10.13.3上的SBCL 1.4.1)
否则,您可以使用非常长的列表调用该函数,并查看结果是否为Stack Overflow错误(递归编译为递归),或列表的长度(使用迭代编译的递归,尾递归)。
更好的是,您可以提供无限长度列表,例如:
(our-length-tr '#1=(1 2 3 . #1#)))
并查看是否产生了Stack Overflow错误(通常几乎是立即生成),或者由于迭代的无限循环而没有产生任何输出。