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.

graph LR A[make-counter] --> B[Creates count=0] B --> C[Returns lambda] C --> D[lambda remembers count] D --> E[Increments count on call]

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!