Skip to content
Lisp Idioms and Style
step 1/4

Reading — step 1 of 4

Learn

~3 min readOptimization, Concurrency, Idioms

Common Lisp is a multi-paradigm language with deep traditions. These idioms separate Lispy code from "Java written in Lisp".

Naming conventions

  • *earmuffs* — special (dynamic) variables
  • +constant+ — true constants
  • -name- — internal/private (no convention; prefix with % or --)
  • predicate? — most use -p suffix: null, evenp, consp, string-equal-p
  • mutator!setf-style mutation: nreverse, nconc, delete (no !; CL has separate destructive names)
  • from-to — converters: string-from-symbol, vector-from-list

Functional vs imperative

Lisp lets you do both. Idiomatic CL favors:

  • Pure functions where possible
  • let over setq for naming intermediate values
  • mapcar/reduce over loop for simple sequence ops
  • loop for complex iteration (it's the practical mini-language)

Don't fight nil

Lisp has nil doing triple duty:

  • The empty list ()
  • The boolean false
  • The "no value" returned from missing things
(if (gethash key table)              ; nil = absent
    (format t "found")
    (format t "missing"))

(if (rest list)                       ; nil = empty
    (process (rest list)))

(if user                              ; nil = absent
    (greet user))

Idiomatic; embrace it.

Avoid setq at toplevel

Use defparameter (always sets) or defvar (only sets if undefined). Top-level setq works but is non-portable in some compilers.

Use sequence functions, not custom loops

Bad:

(let ((result nil))
    (dolist (x lst)
        (when (evenp x)
            (push (* x 2) result)))
    (nreverse result))

Good:

(mapcar (lambda (x) (* x 2)) (remove-if-not #'evenp lst))

Or with loop:

(loop for x in lst when (evenp x) collect (* x 2))

with- macros

For any resource that needs cleanup, write a with- macro:

(with-open-file (s "data.txt") ...)
(with-output-to-string (s) ...)
(with-lock-held (lock) ...)
(my-app:with-db-connection (conn) ...)

The pattern: bind the resource, run the body, ALWAYS clean up. Implement with unwind-protect.

&optional vs &key

(defun greet (name &optional (greeting "Hello"))
    ...)
(greet "Ada" "Hi")

(defun greet (name &key (greeting "Hello"))
    ...)
(greet "Ada" :greeting "Hi")

Keyword args are clearer when you have many — by name, any order. Optional args are positional. For >2 optional args, prefer keyword.

DEFAULTS

(let ((value (or maybe-value default)))
    ...)

or returns the first truthy value — Lisp's idiom for ?? (null coalesce). Watch for 0 and "" though — they're truthy in CL.

Don't write Java in Lisp

If you find yourself building elaborate class hierarchies for simple data, you've left Lisp. Use:

  • defstruct for plain records (faster than CLOS for simple cases)
  • cons cells / lists / hash tables for ad-hoc data
  • CLOS for polymorphism, multi-dispatch, runtime extensibility

Test in the REPL

The REPL is the IDE. Develop a function by:

  1. Define it
  2. Test in REPL with example data
  3. Refine
  4. Once working, save to file

Paul Graham, Peter Seibel, every working CL programmer lives here. SLIME (Emacs) and SLY are the standard editors.

Read other people's Lisp

Quicklisp's libraries are gold. Especially:

  • alexandria — utilities everyone uses
  • iterate — alternative to loop, more macro-extensible
  • bordeaux-threads — portable threads
  • cl-ppcre — regex
  • usocket — sockets
  • hunchentoot / clack — web servers
  • postmodern — Postgres

Browse the source. Common Lisp culture values terse, expressive code over verbosity — it's a different aesthetic from Java/Python.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…