Skip to content

LISP - Closures

A closure is one of the most powerful features in Lisp and other functional languages. It is a function that captures and remembers its lexical environment—the variables and bindings that were in scope when the function was created. This allows a function to maintain state and operate in a context that might otherwise no longer exist.

  • Lexical Scoping: Lisp uses lexical (or static) scope. This means the scope of a variable is determined by its physical placement in the source code. A function can see variables from its containing blocks of code. This is what makes closures possible.
  • First-Class Functions: In Lisp, functions are first-class citizens. They can be stored in variables, passed as arguments to other functions, and returned as results from functions. Closures are a direct result of combining this with lexical scoping.
  • Environment Capturing: When a lambda (an anonymous function) is created, it takes a snapshot of the lexical variables it references. This captured environment is bundled with the function, forming the closure.

The most common way to demonstrate closures is with a function factory: a function that creates and returns other functions. Here, we’ll create an adder factory.

(defun make-adder (n)
"A function factory. Returns a new function (a closure)
that adds N to its argument."
(lambda (x)
(+ x n))) ; This `lambda` captures the value of `n`
;; --- Create and use some adder functions ---
;; Create two distinct closures, each with its own captured environment.
(let ((add-5 (make-adder 5)) ; This closure remembers n=5
(add-10 (make-adder 10))) ; This closure remembers n=10
;; `funcall` is used to invoke a function stored in a variable.
(format t "Calling add-5 with 3: ~a~%" (funcall add-5 3))
(format t "Calling add-10 with 3: ~a~%" (funcall add-10 3))
;; The original `n` from the `let` binding is gone, but the closures still work.
(format t "Calling add-5 again with 100: ~a~%" (funcall add-5 100)))

When you execute the code, it returns the following result:

Calling add-5 with 3: 8
Calling add-10 with 3: 13
Calling add-5 again with 100: 105
  • The make-adder function takes an argument n and immediately returns a lambda function.
  • The returned lambda becomes a closure because it references n, which is part of make-adder’s lexical environment.
  • When we call (make-adder 5), we get a function that is hard-coded to add 5, because it has captured that specific value of n.
  • Each call to make-adder creates a new, independent closure with its own captured environment.

Closures are excellent for creating simple, stateful objects without needing a full defclass. Here’s a function that creates a counter.

(defun make-counter ()
"Creates a counter function that increments its internal count on each call."
(let ((count 0))
(lambda ()
(incf count)))) ; `incf` increments the captured `count` variable
(let ((counter-a (make-counter))
(counter-b (make-counter)))
(format t "Counter A: ~a~%" (funcall counter-a))
(format t "Counter A: ~a~%" (funcall counter-a))
(format t "Counter B: ~a~%" (funcall counter-b))
(format t "Counter A: ~a~%" (funcall counter-a)))
Counter A: 1
Counter A: 2
Counter B: 1
Counter A: 3

As you can see, counter-a and counter-b are independent objects, each with its own private count variable, thanks to the power of closures.