Skip to content

LISP - Functions as Arguments

In Lisp, functions are first-class citizens. This is a cornerstone of functional programming and means that functions are treated just like any other data type, such as numbers or strings.

  • Assignable to Variables: A function can be stored in a variable.
  • Passed as Arguments: A function can be passed as an argument to another function.
  • Returned from a Function: A function can be the return value of another function.

A function that either takes another function as an argument or returns a function is called a Higher-Order Function (HOF). This capability allows for creating highly abstract and reusable code. Common Lisp has many built-in HOFs, such as mapcar, reduce, find-if, and sort.

A lambda expression creates an anonymous function—a function without a name. This is extremely useful for defining a simple, one-off function to be passed as an argument to a HOF, without the need to formally define it with defun.

To pass a function, we must refer to the function object itself, not its name. The special operator #' (a shorthand for (function ...)), is the standard way to do this.

;; A higher-order function that takes a function and a number.
(defun operate-on-five (op)
(funcall op 5))
(defun square (x) (* x x))
(defun cube (x) (* x x x))
;; Pass the `square` function object to `operate-on-five`
(format t "Square of 5 is ~d~%" (operate-on-five #'square))
;; Pass the `cube` function object
(format t "Cube of 5 is ~d~%" (operate-on-five #'cube))
Square of 5 is 25
Cube of 5 is 125

Here, operate-on-five is a HOF. funcall is used to invoke the function object op with the argument 5.

mapcar is a classic HOF that applies a function to each element of a list, returning a new list of the results.

;; `mapcar` takes a function and one or more lists.
(let ((numbers '(1 2 3 4)))
;; Use a lambda expression to define a function on the fly
;; that doubles its argument.
(let ((doubled (mapcar (lambda (x) (* x 2)) numbers)))
(format t "Doubled list: ~a~%" doubled)))
Doubled list: (2 4 6 8)

Two essential HOFs are used to invoke functions dynamically:

  • funcall: Calls a function with arguments provided individually. (funcall #'+ 1 2 3)
  • apply: Calls a function with its arguments supplied as a list. (apply #'+ '(1 2 3))
(let ((args '(10 5)))
;; Using funcall, we have to extract arguments from the list.
(format t "funcall: ~d~%" (funcall #'- (first args) (second args)))
;; Using apply, we can pass the list of arguments directly.
(format t "apply: ~d~%" (apply #'- args)))
funcall: 5
apply: 5
  • Abstraction and Reusability: HOFs let you write generic functions for mapping, filtering, and reducing data, which can then be customized with specific functions. For example, a single filter function can be used to find even numbers, long strings, or active users just by passing a different predicate.
  • Clean and Expressive Code: Using HOFs like mapcar and reduce often leads to more concise and declarative code compared to writing manual loops.
  • Callbacks and Event Handling: A common pattern where a function is registered (passed as an argument) to be called later when a specific event occurs, such as a button click or a network response.