Operators in Common Lisp
Arithmetic, comparison, logical, and assignment operators in Common Lisp - prefix notation, variadic arguments, and exact rational arithmetic
In most languages, operators are syntactic specials — +, -, *, &&, || all live in a grammar separate from function calls, with precedence and associativity rules baked into the parser. Common Lisp throws that distinction out. Every “operator” is just a function (or macro) called in prefix notation inside an S-expression. There is no operator precedence to memorize, because there is nothing to disambiguate: the parentheses already say exactly which call happens first.
This uniformity has practical consequences. Arithmetic functions are variadic — (+ 1 2 3 4 5) is a single call, not chained pairwise additions. Comparison predicates accept any number of arguments and verify the relation across the whole chain — (< 1 2 3) checks 1 < 2 < 3 in one go. And because Common Lisp preserves exact rational arithmetic, (/ 10 3) returns 10/3 rather than silently truncating or producing a floating-point approximation.
In this tutorial you’ll see arithmetic, comparison, logical, and assignment operators, how Common Lisp handles operator precedence through pure structure, and a few Lisp-specific operators like incf, decf, and the setf generalized assignment.
A Tour of Operators
Create a file named operators.lisp:
| |
A few details worth flagging:
(/)returns rationals.(/ 10 3)is10/3, an exact value. Use(float (/ 10 3))if you want3.3333334.andandorare short-circuiting and return the actual value that decided the result, not a normalized boolean.(or nil nil 42)evaluates to42.- Only
NILis false.0,"", and the empty list()(which equalsNIL) — but no other value — count as false.(not 0)returnsNILbecause0is truthy.
Assignment and Mutation
Create a file named assignment.lisp:
| |
setf is Common Lisp’s generalized assignment: any expression that names a storage location (a variable, an array slot, a hash-table entry, a struct field, a CLOS slot) can be the first argument to setf. There is no separate =, []=, or property-set syntax — one operator handles them all.
Precedence — There Is None
Create a file named precedence.lisp:
| |
Because there is no precedence to memorize, the trade-off is more parentheses. In return, every expression’s evaluation order is unambiguous from the page — what you see is the AST.
Running with Docker
| |
Expected Output
Running operators.lisp:
Arithmetic:
(+ 10 5) = 15
(- 10 5) = 5
(* 10 5) = 50
(/ 10 5) = 2
(/ 10 3) = 10/3
(mod 10 3) = 1
(rem -10 3) = -1
(mod -10 3) = 2
(expt 2 10) = 1024
(sqrt 16) = 4.0
(abs -7) = 7
(1+ 41) = 42
(1- 43) = 42
Variadic arithmetic (any number of arguments):
(+ 1 2 3 4 5) = 15
(* 1 2 3 4 5) = 120
(+) = 0
(*) = 1
(- 100 10 5 2) = 83
Comparison:
(= 5 5) = T
(/= 5 6) = T
(< 1 2 3) = T
(< 1 3 2) = NIL
(> 3 2 1) = T
(<= 1 1 2) = T
(zerop 0) = T
(plusp -3) = NIL
(evenp 4) = T
Logical:
(and t t nil) = NIL
(and 1 2 3) = 3
(or nil nil 42) = 42
(or nil nil) = NIL
(not nil) = T
(not 0) = NIL
Running assignment.lisp:
Initial *counter* = 0
After (setf *counter* 10): 10
After (incf *counter*): 11
After (incf *counter* 5): 16
After (decf *counter* 4): 12
Vector after (setf (aref vec 1) 99): #(10 99 30)
Hypotenuse of (3, 4) = 5.0
Running precedence.lisp:
(+ 2 (* 3 4)) = 14
(* (+ 2 3) 4) = 20
x in [1, 10]? T
x in [1, 10]? T
(zerop (mod 100 4)) = T
(> (* 6 7) 40) = T
Key Concepts
- Prefix notation is uniform. Every operator is a function call:
(op arg1 arg2 ...). There is no infix grammar and no precedence table. - Arithmetic operators are variadic.
(+ 1 2 3 4 5)is a single call.(+)and(*)return the additive and multiplicative identities (0and1). - Numeric comparisons chain.
(< 1 2 3 4)checks the entire chain1 < 2 < 3 < 4and returnsTonly if it holds throughout. - Rational arithmetic is exact.
(/ 10 3)returns the rational10/3. You only get floats when at least one operand is a float, or when you explicitly callfloat. andandorshort-circuit and return values, not booleans. They return the actual value that determined the result — useful for default-fallback patterns like(or user-name "anonymous").- Only
NILis false.0, the empty string, and every other value are truthy. The empty list()equalsNILand so is the lone exception. setfis the universal place-setter. One operator assigns to variables, array elements, hash keys, struct fields, and CLOS slots — no special syntax per kind of place.incf/decfare macros, not operators. Because Lisp macros can rewrite source code,(incf x 5)expands into(setf x (+ x 5))at compile time — a pattern impossible to express as a normal function.
Running Today
All examples can be run using Docker:
docker pull clfoundation/sbcl:latest
Comments
Loading comments...
Leave a Comment