The Absolute Silliness of Common Lisp Initial Function Arg Values

We can define functions in Common Lisp that accept optional arguments, which have default values if the user doesn’t provide those arguments.

(defun increment (x &optional (y 1))
    "Returns the given number incremented by the second given number, default 1."
    (+ x y))

CL-USER> (increment 2)
3

We set the initial value of y to be 1, but the initial value doesn’t have to be an atom.

(defun increment (x &optional (y (+ 1 1)))
    "Returns the given number incremented by the second given number, default 2."
    (+ x y))

CL-USER> (increment 2)
4

Here we set the initial value to the return-value of adding 1 and 1.

Common Lisp lets you initialise function args in terms of previously listed function args, so we can do something silly like initialise Y as the square of X, and Z as the square of Y.

(defun increment (x &optional (y (* x x)) (z (* y y)))
  (+ x y z))

CL-USER> (increment 2)
22

If we can initialise args as lisp-forms, and those forms can be defined in terms of previously listed forms, then really we could put an entire lisp program inside a function’s optional args.

Here, I’m defining a function that takes two numbers, x and y, and a function to add them together. The initial value of y is 1, and the initial value of the adder is an anonymous function which reads the values of x and y from the lexical scope it was defined in: the defun-form’s args list.

(defun apply-adder (x &optional (y 1)
                                (adder (lambda () (+ x y))))
  "Applies an optionally provided adder-function to the given x, which has access to the optionally given y."
  (funcall adder))

CL-USER> (apply-adder 2)
3

Lisp has been called the most intelligent way to misuse a computer, and I can prove that by putting a defun form inside of another defun form’s arg-list and have it only be evaluated if an optional argument isn’t passed.

(defun apply-adder (x &optional (y x)
                                (adder (defun my-adder ()
                                         (+ x y))))
  "Applies the given adder-func to the given x and y values. If no adder is passed, the symbol #'my-adder is bound to a newly defun'd function that simply adds x and y."
  (funcall adder))

If the user doesn’t pass a function for adder, then a defun form is called which defines #’my-adder as a globally registered function. Right now, my-adder isn’t defined, we can check this like so:

CL-USER> (fboundp 'my-adder)
NIL

#’fboundp returns T when a function is bound to the given symbol. I’ll now run apply-adder, passing a value for adder so the defun form does NOT evaluate.

CL-USER> (apply-adder 1 2 (lambda (x y) (+ x y 5)))
8

Here I pass a lambda which adds x, y and 5.

‘my-adder is still unbound, since the intial-arg wasn’t evaluated in #’apply-adder.

CL-USER> (fboundp 'my-adder)
NIL

But if I now call #’apply-adder, not passing an adder…

CL-USER> (apply-adder 1)
2

CL-USER> (fboundp 'my-adder)
T

#’my-adder is now bound, and can be called from anywhere in the lisp program.

CL-USER> (my-adder 2 3)
5

The user can define functions in the global scope by not passing an optional argument. And, just to add a cherry on top, everything I’ve done here can be done in lambdas, since they also accept optional arguments.

CL-USER> (fboundp 'my-lambda-adder)
NIL

CL-USER> (funcall (lambda (x &optional 
                               (y x) 
                               (adder 
                                   (defun my-lambda-adder (a b) (+ a b 3))))
                    (funcall adder x y))
                  2)
7

CL-USER> (my-lambda-adder 5 4)
12

An anonymous function constructed and registered a global function from within its argument list, based on prior arguments, then bound that new global function to a local variable and called it, because an optional argument wasn’t passed. Nuts.

Leave a Reply

Your email address will not be published. Required fields are marked *