Common Lisp

Table of Contents

1. Trivia (pattern matching library)

In Quicklisp.

1.1. Solved: Unbound pattern errors for alist, plist, property

Encountered for version trivia-20230618-git in the Quicklisp dist.

I was loading trivia but not use-ing it. The patterns listed above, unprefixed, would cause errors.

Two solutions:

  • (use-package :trivia) or equivalent, and don't namespace anything.
  • (trivia:match <...> ((trivia:property <...>)))

In short, patterns must be namespaced just like macros and functions. trivia's macros don't pull them in automatically.

1.2. otherwise works as expected

As in CL's cond, use otherwise as a default last branch in a match or similar expression.

1.3. plist takes a fuzzy subsequence of the expected list

For example:

(ql:quickload :trivia)
(trivia:match '(:a 1 :b 2 :c 3)
  ((trivia:plist :c 3 :a 1) 'ok))

2. Woo (web server)

2.1. System dependencies

  • libev: available as libev on Alpine 3.20.

3. Error handling: Conditions and restarts

Throw a condition, instantiated from a condition type, to signify an unusual situation.

Define a condition type like this:

(define-condition my-custom-error (error)
  ;; slot specs & options as in DEFCLASS
  )

Signal a condition with any of the following:

Function If condition not handled
ERROR Invoke debugger
WARN Print a warning
SIGNAL Return nil

Prefer error for errors because it ensures the condition is handled before computation continues.

A restart is a method of recovery from a signaled condition. Define a restart exactly where the flow of control should resume. Use restart-case.

Handle errors from higher up on the call stack using handler-bind. Functions bound by handler-bind can use invoke-restart to manually invoke any restart the programmer thinks might be available.

3.1. Example from Practical Common Lisp

 1  (define-condition malformed-log-entry-error (error)
 2    ((text :initarg :text :reader text)))
 3  
 4  (defun log-analyzer ()
 5    (handler-bind ((malformed-log-entry-error
 6                     #'(lambda (c)
 7                         (invoke-restart 'skip-log-entry))))
 8      (dolist (log (find-all-logs))
 9        (analyze-log log))))
10  
11  (defun analyze-log (log)
12    (dolist (entry (parse-log-file log))
13      (analyze-entry entry)))
14  
15  (defun parse-log-file (file)
16    (with-open-file (in file :direction :input)
17      (loop for text = (read-line in nil nil) while text
18            for entry = (restart-case (parse-log-entry text)
19                          (skip-log-entry () nil))
20            when entry collect it)))
21  
22  (defun parse-log-entry (text)
23    (if (well-formed-log-entry-p text)
24        (make-instance 'log-entry ...)
25        (restart-case (error 'malformed-log-entry-error :text text)
26          (use-value (value) value)
27          (reparse-entry (fixed-text) (parse-log-entry fixed-text)))))

Notice: Two functions establish restarts. Parse-log-file establishes one near line 18, and parse-log-entry establishes two near line 25.

The error call on line 25 is the only part of this code that can signal a condition. When that happens, all three restarts are available. The condition travels up the stack looking for a handler. If no function had one, we'd enter the debugger with at least our three custom restarts available. (Entering the debugger is a feature of ERROR. See WARN and SIGNAL.) But log-analyzer has a policy for a malformed-log-entry-error. It directly invokes the restart skip-log-entry. Handler-bind is used instead of handler-case; the latter would unwind the stack to log-analyzer. Control flow jumps to line 19, where the restart is defined. That expression returns nil, and the loop in parse-log-file continues as if nothing strange has happened.

4. Dynamic/special variables

Usually, variables are globally special or not special at all. A programmer must work hard to create exceptions.

Special denotes that, even when the variable is rebound in forms like let, any code run during the dynamic extent of that form can see the dynamic binding.

Defvar and defparameter both create dynamic variables. "Dynamic" and "special" are practically synonyms. Defparameter overwrites any existing value; defvar doesn't. That difference influences interactive development: A value assigned with defvar lasts an entire Lisp session. A value assigned with defparameter is overwritten when code is reloaded.

So, true to its name, defparameter is best suited for values which tweak the overall program behavior. Defvar is good for counters and other cumulative state.

What about runtime configuration variables like in Emacs? I'm thinking defvar. It sounds too jarring to reload those upon reloading a system.

Author: Ty Kozic

Created: 2025-01-04 Sat 22:09

Validate