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.