Is there a style convention for common Lisp recursive helper functions?

I would like to know if there is a style guide published by ANSI or project authors or other influential authority for Lisp functions that are implemented using recursive helper functions that take additional parameters that the person calling the function needs to think about. Here is the simplest example that I can think of. Which of these three, if any, is preferred in the standard style guide for generic Lisp (if any)?

(defun factorial (n)
   (defun tail-factorial (n m)
      (if (= n 1)
          m
          (tail-factorial (- n 1) (* n m))))
   (tail-factorial n 1))

This seems unpleasant due to the function declaration in the function declaration. I would use a lambda to not call the helper function and folding things, but I don't understand how to recurs in a lambda expression by calling it. Even if there is a way to get an anonymous function call, it seems that it will be messy, especially if the assistant needs an assistant, an assistant is needed ...

Another alternative is to declare the guy tail first (or after, but that makes sbcl complain):

(defun tail-factorial (n m)
   (if (= n 1)
      n
      (tail-factorial (- n 1) (* n m))))
(defun factorial (n)
   (tail-factorial n 1))

, -, , - , . , , , , . , , , ...

:

(defun factorial (n &optional (m 1))
   (if (= n 1)
      m
      (factorial (- n 1) (* n m))))

, . -, , . , , , .

, , , , , - , , , - , . SBCL , , ANSI , .

, , . , ( ..), , , . - -, , .

!

+4
3

defun . defun defun, . factorial, : labels

(defun factorial (n)
   (labels ((tail-factorial (n m)
              (if (= n 1)
                  m
                  (tail-factorial (- n 1) (* n m)))))
     (tail-factorial n 1)))

Lisp , . loop .

, , , - . .

+8

, , , do . do , , ( ), . ( , .) do Scheme let, , t . , , . :

(defun factorial (n)
  (do ((n n (1- n))    ; n starts as n, and is (1- n) on next iteration
       (m 1 (* n m)))  ; m starts at 1, and is (* m n) on next iteration
      ((= 1 n) m)))    ; if (= 1 n), then return m, else go to next iteration

CL-USER> (factorial 11)
39916800

named let, Common Lisp:

(defmacro nlet (name bindings &body body)
  `(labels ((,name ,(mapcar 'first bindings)
              ,@body))
     (,name ,@(mapcar 'second bindings))))

CL-USER> (macroexpand-1 '(nlet factorial ((n n) (m 1))
                          (if (= n 1) m
                              (factorial (1- n) (* m n)))))
(LABELS ((FACTORIAL (N M)
           (IF (= N 1)
               M
               (FACTORIAL (1- N) (* M N)))))
  (FACTORIAL N 1))

, Common Lisp , , . nlet, , , , ; , , .

, nlet . , , -

(nlet frob ((f #'frob))
   ...)

# 'foo . , , , , Scheme named let . , :

(defmacro nlet (name bindings &body body)
  `(funcall (labels ((,name ,(mapcar 'first bindings)
                       ,@body))
              #',name)
            ,@(mapcar 'second bindings)))

CL-USER> (macroexpand-1 '(nlet factorial ((n n) (m 1))
                          (if (= n 1) m
                              (factorial (1- n) (* m n)))))
(FUNCALL
 (LABELS ((FACTORIAL (N M)
            (IF (= N 1)
                M
                (FACTORIAL (1- N) (* M N)))))
   #'FACTORIAL)
 N 1)

(defmacro nlet (name bindings &body body)
  `(funcall (labels ((,name ,(mapcar 'first bindings)
                       ,@body))
              #',name)
            ,@(mapcar 'second bindings)))
+6

Good style:

  • useful function name
  • documentation line
  • argument type checking
  • no controls in portable code -> basically avoid tail recursive code

Example:

(defun factorial (n &aux (result 1))
  "Calculates the factorial N! for N >= 0"
  (check-type n (integer 0 *))
  (loop for i from 1 to n
        do (setf result (* result i)))
  result)

Using:

CL-USER 94 > (apropos "factorial")
FACTORIAL (defined)

CL-USER 95 > (documentation 'factorial 'function)
"Calculates the factorial N! for N >= 0"

CL-USER 96 > (factorial 7)
5040
+4
source

All Articles