Functional Programming in Lisp: Core Concepts and Examples
Lisp isn’t just a language—it’s a vibe. A philosophy wrapped in parentheses, whispering sweet nothings about lambdas and closures while sipping symbolic espresso. Let’s explore why functional programming in Lisp feels like giving your code a superpower glove adorned with recursive gems and higher-order sparkles.
Why Lisp Devs Have All the Fun
Lisp treats functions like first-class celebrities. They can be:
- Passed as arguments to other functions
- Returned as values
- Stored in data structures
- Created dynamically at runtime This transforms coding from “instruction-following” to “composing a symphony.” For example, here’s how you’d pass a function as an argument:
(defun apply-twice (fn x)
(funcall fn (funcall fn x)))
(apply-twice (lambda (n) (* n 2)) 5) ; Returns 20
The lambda
creates an anonymous function that doubles its input. We pass this to apply-twice
, which applies it twice to 5 → 5×2=10 → 10×2=20. Elegant, no?
Closures: The Secret Sauce
Closures are Lisp’s “memory pills”—they remember their birth environment. Imagine a counter that privately tracks its state:
(defun make-counter ()
(let ((count 0))
(lambda ()
(setf count (1+ count)))))
(defvar my-counter (make-counter))
(funcall my-counter) ; → 1
(funcall my_counter) ; → 2
Each time we call my-counter
, it increments its private count
variable. The closure retains access to count
even after make-counter
exits.
This environment-capturing magic powers stateful logic without mutable global variables.
Higher-Order Functions: The Glue
Lisp’s mapcar
and reduce
are the ultimate data transformers. Let’s decode a list of emoji strings:
(mapcar (lambda (s)
(length (remove-if-not #'alpha-char-p s)))
'("🍕Pizza!" "🚀Rockets!" "👾Aliens!"))
; → (5 7 6)
Here, mapcar
applies our lambda to each string, counting only alphabetic characters. The remove-if-not
filters non-alphabet characters—functional composition at work!
Lexical vs. Dynamic Scope: The Duel
Lisp primarily uses lexical scope (variables are bound where they’re defined), but supports dynamic scope too. Compare:
;; Lexical scope
(let ((x 10))
(defun show-x () x))
(let ((x 20)) (show-x)) ; → 10 (uses definition-time x)
;; Dynamic scope
(defvar *y* 30)
(defun show-y () *y*)
(let ((*y* 50)) (show-y)) ; → 50 (uses call-time *y*)
The *y*
convention indicates dynamic variables. Lexical scope avoids surprises—functions see variables from their definition site, not call site.
Functional Patterns in Practice
Pattern 1: Custom reducers
Sum even numbers in a list:
(reduce #'+
(remove-if-not #'evenp '(1 2 3 4 5 6))
:initial-value 0)
; → 12
Pattern 2: Function generators
Create multipliers:
(defun multiplier (n)
(lambda (x) (* x n)))
(defvar double (multiplier 2))
(funcall double 8) ; → 16
When Functional Meets Quirky
Why did the Lisp function break up with its girlfriend?
Because it had too many arguments and couldn’t commit!
…I’ll see myself out.
But seriously—embracing functional paradigms in Lisp is like discovering your code has a “composition over instruction” setting. Your functions become reusable LEGO blocks, closures handle state with amnesia-proof elegance, and higher-order functions turn data wrangling into poetry. Now go (funcall)
your creativity!