Labels and named lets if your Lisp optimizes tail calls
Just as a matter of playing "stupid Lisp tricks", I'd point out that in Scheme, the analog to
sum a b = let
loop s i = if i > b then sum else loop (s + i) (i + 1)
in loop 0 a
is letrec or a named 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, because Scheme is a Lisp-1, gives you the functionality of labels
, which Baggers described. The named let can be done in Common Lisp with a macro around 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 and Do* should be efficient everywhere
However, tail calls aren't necessarily optimized away in Common Lisp, so this kind of recursion for iteration isn't all that common. The iterative analog is do
:
(defun sum (a b)
(do ((s 0 (+ s i))
(i a (+ i 1)))
((> i b) s)))
Loop works too
You can use loop
too, but it's a bit more verbose (but also probably more readable if you're familiar with do
:
(defun sum (a b)
(loop
for s = 0 then (+ s i)
for i = a then (+ i 1)
when (> i b) return s))
let
when the name is invoked in a tail position. It ends up being a go-to or a tail call, e.g. iteration in disguise. What should be noted is that Common Lisp doesn't guarantee tail calls, so one needs to excplicitly use iterative approaches to avoiding stack overflow. – Emmettemmey... then s else loop ...
instead of... then sum else loop ...
. OP's version wouldn't compile in Haskell. GHC would have this understood as infinite type. – Occupational