Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

helper nested functions in CL

I used to write nested helper functions (which, btw, ocasionally used parameters of outer functions and are recursive) in Haskell like this (loop):

sum a b = let
  loop s i = if i > b then s else loop (s + i) (i + 1)
  in loop 0 a

What is the clearest analog for this in Common Lisp?

I searched around here and found some discussions focused on returning functions from functions (and the problems which may arise when trying to call such “returned” functions) which is not quite the same situation, as far as I can see.

like image 220
Artem Pelenitsyn Avatar asked Oct 23 '13 07:10

Artem Pelenitsyn


2 Answers

Labels is used to define local functions.

CL-USER> (defun thing (x)
           (labels ((helper (y) (loop for i from x to y collect i)))
             (helper 5)))
THING
CL-USER> (thing 1)
(1 2 3 4 5)

It is kind of like a let* statement for functions as you can define multiple local functions. So here we have helper and double-it being defined.

(defun thing (x)
   (labels ((helper (y) (loop for i from x to y collect i))
            (double-it (num) (* 2 num)))
     (helper (double-it 10))))

Also you could use lambdas. Which in this instance is fairly tidy, though I still prefer labels for readability in this case.

CL-USER> (defun another-test (x)
           (funcall #'(lambda (y) (loop for i from x to y collect i)) 10))
ANOTHER-TEST

CL-USER> (another-test 2)
(2 3 4 5 6 7 8 9 10)

Labels can also be used recursively:

CL-USER> (defun test-recurse (x)
           (labels ((rec-list (count) 
                      (when (< count x) 
                        (cons count (rec-list (+ 1 count))))))
             (rec-list 0)))
TEST-RECURSE
CL-USER> (TEST-RECURSE 10)
(0 1 2 3 4 5 6 7 8 9)

Hope it helps!

like image 148
Baggers Avatar answered Oct 18 '22 23:10

Baggers


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))
like image 2
Joshua Taylor Avatar answered Oct 19 '22 00:10

Joshua Taylor