I learned to read math notation before I learned to read code. By the time I sat down with a programming book in college, I had spent years writing function compositions, defining sets by predicates, and turning one expression into another by substitution. The shape of math was already in my head.
The first language I tried to learn was Java. The mismatch was immediate. The math I knew did not have classes; it did not have getters and setters; it did not have a main method with a String[] args parameter. The math I knew had functions, and the functions composed.
A few years later I discovered Scheme. I had the opposite reaction. This was not a programming language pretending to be math. This was math that happened to run.
Functions all the way down
Lisp’s primary abstraction is the function. Not the object, not the class, not the module: the function. Everything else is built on top.
The intellectual root is the lambda calculus, which is older than computers themselves: a formal system with one operation (function application) and one rule (substitution). McCarthy took the lambda calculus and gave it a syntax you could type. The language fell out almost immediately.
A data structure is a function in disguise; a list is a constructor and two accessors. An object, when you need one, is a closure over some state. A control structure is a procedure that takes other procedures as arguments. The vocabulary is small and the recursion is total.
For someone with a math background, this is the language sitting where you expect it to sit. In math, a function is the unit of thought. You name it, you compose it, you reason about it. You do not wrap it in a class to give it a home. Lisp agrees.
The syntax is the abstract syntax tree
Most languages have a syntax that the compiler parses into a tree. Lisp skips the parse. The syntax is the tree. Parentheses are not punctuation; they are the structure.
(+ 1 (* 2 3))
This is a tree with + at the root and two children: the leaf 1 and a subtree rooted at *. Prefix notation, the way a mathematician writes an abstract expression. There is nothing to parse around.
The shape also reads close to natural speech. (add 1 2) is verb-then-objects, the way you would say it: “add 1 and 2.” Java’s Integer.sum(1, 2) is a noun, a dot, and a verb that takes arguments, which is no English clause I recognize. The prefix order matches how a person describes an action.
The deeper payoff is that code is data. The expression above is also a list of three elements. A program can read it, transform it, and evaluate the result. The boundary between “the program” and “the program’s input” disappears.
Macros and the death of patterns
The first time I wrote a macro, I understood what people meant by patterns are where you run out of language.
A design pattern is a workaround for something the language cannot say directly. Visitor is a workaround for the absence of multimethods. Strategy is a workaround for the absence of first-class functions. The Gang of Four book is, read uncharitably, a list of features Java did not have in 1994.
In Lisp, when you find yourself writing the same shape of code twice, you write a macro. The macro does not look like a workaround; it looks like new syntax. The third time you need the pattern, you use the syntax. The pattern is not a pattern anymore; it is a word in the language.
This is the deepest version of the DSL idea, and Lisp is where the DSL idea was born. You do not write a program in the language. You write a language for the problem, then you write the program in that. The line between “library” and “language extension” stops being load-bearing.
Concepts you stop needing
Working in Lisp deletes concepts I had thought were fundamental.
I do not need a for loop; I have map, filter, and reduce. I do not need a class hierarchy; I have closures. I do not need a builder pattern; I have functions that return functions. I do not need a null check at every callsite; I have option types and pattern matching.
The list is not “Lisp has cleverer ways to do these things.” The list is “Lisp does not have these things, and the absence is not a hole.” The concept stopped being necessary when the abstraction underneath it got simpler.
This is the part of the language I find most clarifying. Subtraction as design.
Pure functions and honest side effects
A Lisp program tends to separate the part that computes from the part that touches the world. Pure functions in the middle; effects at the edges. The middle is easy to test, because a pure function is its inputs and its output and nothing else. The edges are explicit, because the language gives you nowhere to hide them.
Clojure makes the discipline more obvious than Scheme does: immutable data by default, side effects routed through specific constructs. But the shape is the same in any Lisp written with care. The functional core sits behind a thin imperative shell.
Tests in this shape are almost boring to write. You feed the function an input, you assert on the output, you move on. No setup, no teardown, no mocks for collaborators that did not need to exist in the first place. The test mirrors the function, because the function is the unit.
Where Lisp lives now
In practice, very few teams pick a Lisp. Clojure has the largest commercial footprint; Racket lives in research and teaching; Common Lisp persists in pockets. Scheme is mostly a teaching language and a personal language.
That is a shame, because Lisp is one of the better modeling languages I know. You can describe a domain in it the way a domain expert would describe it, and the description runs. You build a vocabulary for the business, then write the business logic in that vocabulary. The code reads like the domain because the language was made to fit it.
The argument against Lisp in industry is rarely technical; it is about hiring, tooling, and momentum. Those are real constraints. They do not make the language worse; they make it less available.
A love letter, not a pitch
I am not going to tell you to rewrite your service in Clojure. I am not even going to tell you to use Lisp at work.
This post is a love letter to the ideals: small core, functions as the unit of thought, code and data sharing a shape, language extension instead of patterns, pure functions at the center and effects at the edges. Most of the practices I rely on day to day, in any language, are practices the Lisp tradition worked out before I was born.
The ideas keep getting rediscovered. Immutable data structures, first-class functions, algebraic effects, persistent collections, structural editing: each was new and exciting somewhere recently, and each was sitting in a Lisp paper from the 1960s or 1970s. If you read enough Lisp history, you start to spot tomorrow’s rediscoveries in last decade’s footnotes.
If you want to find the ideas the long way, the way I did, read SICP. Structure and Interpretation of Computer Programs. The Abelson and Sussman lectures are on YouTube and they hold up. You will work through small problems in Scheme and come out with a different mental model for how programs are built. Most of the patterns you reach for at work will look smaller after.
The reason to learn a language you will not use professionally is the perspective it gives you on the languages you do use. Lisp changes what you think is possible. After that, the question in any other language becomes: how close to that can I get here, and what is in the way?
That is the thing worth borrowing. Not the parentheses; the perspective.
(❤️ I Lisp)
Top comments (0)