Is there an idiomatic way for defining a function via function composition in Common Lisp? I am currently setting the fdefinition of a symbol like this:
(setf (fdefinition 'key-fn) (util:compose #'util:make-keyword #'str:upcase #'str:param-case))
util in this case contains the Alexandria and Serapeum utility libraries.
Probably the most idiomatic solution would be to write a little higher-order function that composes functions.
A simple implementation could be like:
(defun compose (f g)
#'(lambda (x) (funcall f (funcall g x))))
The compose
function takes as arguments two functions (each which take only one argument) and returns a function that takes one argument.
CL-USER> (funcall (compose #'1+ #'1+) 0)
2
The above will work for many common use-cases, but a slightly more general version could be written that composes an arbitrary number of function arguments. Paul Graham writes something like this function in his book ANSI Common Lisp in Chapter 6.6 about higher-order functions:
(defun compose-many (&rest fns)
(destructuring-bind (inner-fn . outer-fns) (reverse fns)
#'(lambda (&rest args)
(reduce #'(lambda (val fn) (funcall fn val))
outer-fns
:initial-value (apply inner-fn args)))))
The compose-many
function takes one or more function arguments to be composed. The inner-most function can take an arbitrary number of arguments itself, but all remaining functions must take only one argument. The function returned by compose-many
takes an arbitrary number of arguments to be passed to the inner-most function.
CL-USER> (funcall (compose-many #'1+ #'1+ #'*) 2 3)
8
In the last example #'*
can take any number of arguments, but #'1+
takes exactly one argument.
Since symbols in Common Lisp have value cells and function cells there isn't a simple def
-like form. The OP posted method of using setf
with fdefinition
is the way to accomplish this.
You could write a defcompose
macro that makes this all more convenient:
(defmacro defcompose (fname &rest args)
`(progn (intern (string-upcase ',fname))
(setf (fdefinition ',fname) (compose-many ,@args))))
This defcompose
macro interns a symbol in the current package before binding a composed function to its function cell.
CL-USER> (defcompose my-composition #'1+ #'1+ #'*)
#<FUNCTION (LAMBDA (&REST ARGS) :IN COMPOSE-MANY) {1001D4698B}>
CL-USER> (my-composition 2 3)
8
Often you just want to compose functions for a one-off use, e.g., to pass a function argument to another function. In these cases there is no need to bind the function to a symbol in the global name space, and you can just use something like the compose-many
function above.
If you do want to create globally available functions using composition you might also want to add documentation strings. This is simple to add, but here the functions to be composed are passed explicitly in a list to distinguish them from an optional documentation string:
(defmacro defcompose (fname args &optional docs)
`(progn (intern (string-upcase ',fname))
(setf (fdefinition ',fname) (compose-many ,@args))
(setf (documentation #',fname 'function) ,docs)))
CL-USER> (defcompose my-composition (#'1+ #'1+ #'*)
"Multiplies its arguments together and adds two to the result.")
"Multiplies its arguments together and adds two to the result."
CL-USER> (my-composition 3 2)
8
CL-USER> (documentation #'my-composition 'function)
"Multiplies its arguments together and adds two to the result."