あなたのLispがテールコールを最適化する場合のラベルと名前付きLets
「ばかげた Lisp のトリック」をするのと同じように、Scheme では、
sum a b = let
loop s i = if i > b then sum else loop (s + i) (i + 1)
in loop 0 a
letrec または名前付き let:
(define (sum a b)
(letrec ((loop (lambda (s i)
(if (> i b)
s
(loop (+ s i) (+ i 1))))))
(loop 0 a)))
(define (sum a b)
(let loop ((s 0) (i a))
(if (> i b)
s
(loop (+ s i) (+ i 1)))))
Letrec は、Scheme が Lisp-1 であるため、Baggers が説明した の機能を提供labels
します。名前付き let は、Common Lisp でマクロを使用して実行できますlabels
。
(defmacro named-let (name bindings &body body)
`(labels ((,name ,(mapcar 'first bindings)
,@body))
(,name ,@(mapcar 'second bindings))))
(pprint (macroexpand
'(named-let loop ((s 0) (i a))
(if (> i b)
s
(loop (+ s i) (+ i 1))))))
;; (LABELS ((LOOP (S I)
;; (IF (> I B)
;; S
;; (LOOP (+ S I)
;; (+ I 1)))))
;; (LOOP 0 A))
Do と Do* はどこでも効率的であるべき
しかし、末尾呼び出しは Common Lisp では必ずしも最適化されていないため、反復のためのこの種の再帰はそれほど一般的ではありません。反復アナログはdo
次のとおりです。
(defun sum (a b)
(do ((s 0 (+ s i))
(i a (+ i 1)))
((> i b) s)))
ループも機能します
あなたも使うことができますloop
が、もう少し冗長です(しかし、あなたが精通しているなら、おそらくもっと読みやすいでしょうdo
:
(defun sum (a b)
(loop
for s = 0 then (+ s i)
for i = a then (+ i 1)
when (> i b) return s))