Capstone
Capstone

Reputation: 2282

Optional function argument with default value in Common Lisp

Here's a function I was writing that will generate a number list based on a start value, end value and a next function.

(defun gen-nlist (start end &optional (next #'(lambda (x) (+ x 1))))
    (labels ((gen (val lst)
        (if (> val end) 
            lst
            (cons val (gen (next val) lst)))))
    (gen start '())))

However when entering it into the SBCL repl I get the following warnings:

; in: DEFUN GEN-NLIST
;     (SB-INT:NAMED-LAMBDA GEN-NLIST
;         (START END &OPTIONAL (NEXT #'(LAMBDA (X) (+ X 1))))
;       (BLOCK GEN-NLIST
;         (LABELS ((GEN #
;                    #))
;           (GEN START 'NIL))))
; 
; caught STYLE-WARNING:
;   The variable NEXT is defined but never used.

;     (NEXT VAL)
; 
; caught STYLE-WARNING:
;   undefined function: NEXT
; 
; compilation unit finished
;   Undefined function:
;     NEXT
;   caught 2 STYLE-WARNING conditions

Somehow it does see the variable next as defined, but not used. And where I do use it, as in (next val), it's an undefined function?!

Clearly I'm doing something wrong here. I just can't figure what or how. Is this the correct way of specifying optional function arguments with default values?

Upvotes: 5

Views: 2247

Answers (3)

Capstone
Capstone

Reputation: 2282

In case if anyone ever does need to see the functioning code in full, thanks to the answer above and the comments, here's what I ended up with:

(defun gen-nlist (start end &optional (next #'1+) (endp #'>))
    (labels ((gen (val)
        (if (funcall endp val end) 
            '()
            (cons val (gen (funcall next val))))))
      (gen start)))

And here's the tail recursive version which doesn't suffer from a function call stack overflow, when attempting to create large lists:

(defun gen-nlist (start end &optional (next #'1+) (endp #'>))
    (labels ((gen (val lst)
        (if (funcall endp val end) 
            (reverse lst)
            (gen (funcall next val) (cons val lst)))))
      (gen start '())))

Usage examples:

> (gen-nlist 0 10)

(0 1 2 3 4 5 6 7 8 9 10)

> (gen-nlist 0 10 #'1+ #'equal)

(0 1 2 3 4 5 6 7 8 9)

> (gen-nlist 0 -10 #'1- #'<)

(0 -1 -2 -3 -4 -5 -6 -7 -8 -9 -10)

> (gen-nlist -10 0)

(-10 -9 -8 -7 -6 -5 -4 -3 -2 -1 0)

Upvotes: 1

Rainer Joswig
Rainer Joswig

Reputation: 139251

The simple recursive version has a main problem: stack overflow for long lists. It's useful as a learning exercise, but not for production code.

The typical efficient loop iteration would look like this:

(defun gen-nlist (start end &optional (next #'1+) (endp #'>))
  (loop for i = start then (funcall next i)
        until (funcall endp i end)
        collect i))

Upvotes: 8

Terje D.
Terje D.

Reputation: 6315

You have defined the optional argument the correct way, but as Common Lisp is a Lisp-2, it distinguishes between functions and variable values. The optional function is available as a variable, and has to be called using funcall.

Replace

(cons val (gen (next val) lst))

with

(cons val (gen (funcall next val) lst))

and both the warning about the unused variable and the warning about the undefined function will disappear.

BTW: Your default function can be replaced with #'1+

Upvotes: 5

Related Questions