Unexpected (to me) behaviour in Lisp sub-typing

Unexpected (to me) behaviour in Lisp sub-typing

I just encountered some unexpected behaviour in how Common Lisp (or at least SBCL) handles array sub-typing.

Suppose you have a structure declaration like this:

  (defstruct A
    (a #1A() :type (simple-array integer)))

We can create instances of this struct by providing an initial value, and it will be type-checked.

  (make-a :a #1A(1 2 3))
#S(A :A #(1 2 3))

We can also omit the argument and get the default specified in the declaration:

  (make-a)
#S(A :A #())

So far so good. But suppose we now specialise the simple-array type against a type such as (unsigned-byte 16) (the type of unsigned 16-bit integers).

  (defstruct B
    (b #1A() :type (simple-array (unsigned-byte 16))))

  (make-b :b #1A(1 2 3))

This generates an error, as does using the default value.

Why is this? I eventually tracked the problem down to the fact that the expression #1A() is given type (simple-vector 0) while the expression #1A(1 2 3) is given type (simple-vector 3), and neither of these are sub-types of (simple-array (unsigned-byte 16)):

  (type-of #1A())
(SIMPLE-VECTOR 0)
  (type-of #1A(1 2 3))
(SIMPLE-VECTOR 3)
(typep #1A(1 2 3) '(simple-vector 3))
T
(typep #1A(1 2 3) '(simple-array (unsigned-byte 16)))
NIL

So the typing works for integer (and indeed for number and float) but not for the constrained types like (unsigned-byte 16) – although it does work for unsigned-byte (with no width).

Interestingly the tests fail for empty arrays as well:

  (typep #1A() '(simple-array (unsigned-byte 16)))
NIL

I assume that the behaviour is because the compiler is at liberty to optimise the storage of arrays with specific types, and doing so makes it awkward to compare against types like simple-vector that don’t contain their element type. This would also explain why the check succeeds for types like integer, whose values are stored boxed and so can represented uniformly.

There’s a simple way around this, of course, once you know what the problem is: make the type of the value explicit rather than relying on the reader macro. For example:

  (defstruct C
    (c (make-array '(0) :element-type '(unsigned-byte 16)) :type (simple-array (unsigned-byte 16))))

  (make-c)
#S(C :C #())

and also:

  (make-c :c (make-array '(3) :element-type '(unsigned-byte 16) :initial-contents (alexandria:iota 3)))
#S(C :C #(0 1 2))

I can’t find this behaviour detailed in the manuals (or anywhere else) though. However, it seems to be related to the upgraded array element type. When an array is created, the implementation is allowed to use a different type to the one specified in its representation, which must be a supertype of the type requested. For types like integer and unsigned-byte this is t, the top type in the lattice:

  (upgraded-array-element-type 'unsigned-byte)
T

But for the “constrained” types the upgraded type is the type itself:

  (upgraded-array-element-type '(unsigned-byte 16))
(UNSIGNED-BYTE 16)

This suggests to me that the compiler (SBCL) is indeed optimising the representation and then also performing more detailed type-checking for elements.

A road to Lisp — Which Lisp?

A road to Lisp: Which Lisp?

A Road to Lisp: Which Lisp

A blog post reviewing four dialects of Lisp that a beginner wanting to start with Lisp can choose: Common Lisp, Clojure, Racket, and ELisp. The author, Elia Scotto, interestingly doesn’t include Scheme, although he does present Racket as a Scheme dialect (which of course it is).

Does the choice too make initially matter?

Dialect matters, but not as much as a beginner might think. Learning Lisp is about learning a new type of programming. A new way of thinking about problems using code. You will learn the fundamental concepts with any dialect. Then, once you have learned one, it will be relatively easy to switch to another.

I think most Lisp devotees would agree with the sentiment that it’s about a new relationship with code; I’m not totally convinced that Racket is a good place to start, given that it’s essentially several languages; and ELisp only makes sense if you are already a devoted Emacs user, otherwise the learning curve will be nearly vertical.

Scotto’s conclusion is that Clojure is the safest first choice, giving you a modern syntax and full-stack environment with access to all the Java libraries, while Common Lisp gives you fast native compilation within a large, stable, and rock-solid ecosystem, and Racket is a good basis for language research.

For my own part I came to Scheme first when I was an undergraduate, then picked up ELisp when I started using Emacs, and then learned Common Lisp when I started wanting to write significant code – and there I’ve stayed with the latter two, quite happily. It’s certainly the case that it’s been easy to move between these dialects, modulo some differences that are purely syntactic. And it’s certainly true that Lisp changed my relationship to code in a deep and persistent way.

Antwerp

Michael Pye (2021)

Antwerp is, to the modern eye, a strange choice for an urban biography. But it’s a place that can make a fair claim to having invented a lot of the modern world, notably the financialisation of trade and especially of art: the first place where one bought paintings from dealers rather than commissioning them from the studios. A place that tried valiantly to separate trade from religion, to protect its minorities and resist anti-Protestant edicts while still being rules from Spain, and to ride-out the waves of regulation and repression that washed around it. It was a bold experiment in trying to re-become a city-state, at a time when that simply wasn’t possible any more.

It’s impossible to read this book and not feel its author’s debt to Barbara Tuchman, both in the general style of writing and in the sharp eye for personal detail. There are a couple of quite detailed biographies, of an architect and a printer/publisher, each of which is both engaging gossipy and carefully chosen to show the changes that each man was essential in bringing in. In that way it’s a very personal book that seeks to illustrate the history of the city by the histories of individuals, gleaned from a formidable amount of primary research: Pye seems to have read every letter, book, and bill relating to the period.

I don’t think it quite succeeds, despite the great writing and scholarship: the city’s history seems confused and lost amid the detail, it’s hard to follow the timeline and understand the sweep of the politics and the events shaping it. But it’s still a superb read, packed full of anecdotes and the seeds or modernity.

3/5. Finished Saturday 1 August, 2026.

(Originally published on Goodreads.)

Hothouse Earth: An Inhabitant’s Guide

Bill McGuire

A horror story, with the additional horror of being entirely backed by science.

I’m writing during the 2026 summer heatwaves in the UK, and this was really not the time to be reading this book. It lays bare the best estimates for changes caused by global heating to everything from diseases and food production to sea levels and increased earthquakes (caused by the land rebounding after the compression caused by ice is removed). The world envisaged is not entirely unlivable, but that’s only a slight comfort given the tribulations to come.

Had we acted on global heating at the time of the Stern review of 2006 it might have been possible to avoid the worst at a reasonable economic cost. (McGuire, interestingly, doesn’t mention Stern, whose conclusions have often been criticised for being understated in both risks and costs.) We’re now in a position where we have to make much more expensive and difficult carbon reductions to avoid terrible harms, while also having to incur massive costs to mitigate the harms that are already baked-in to the climate system.

It’s not a book that’s entirely without hope: the last chapter paints two pictures of London later in the century under different emission reduction scenarios that highlight that a warmer world would be liveable (and even pleasant) – but only after actions that frankly seem beyond us as a civilisation. McGuire points to the hypocrisy of world leaders as the COP26 conference, promising cuts while permitting more drilling: As I’m writing, more drilling permits are being issued by the new UK administration, and AI data centres are being proposed that will require local gas-fired power stations exceeding the current generation capacity of the entire UK electricity grid. It seems we still have to grasp the scale of changes required, with those changes growing the longer we wait.

5/5. Finished Saturday 18 July, 2026.

(Originally published on Goodreads.)

Beautiful Racket: An introduction to language-oriented programming using Racket

Beautiful Racket: An introduction to language-oriented programming using Racket

nil

Matthew Butterick. Beautiful Racket: An Introduction to Language-Oriented Programming Using Racket. 2024.

Racket positions itself as a “programmable programming language” – more so than Lisp’s usual claim to this title – which makes it attractive for exploring multi-paradigm and multi-language programming.

Racket’s main claim to a niche is allowing a programmer to define new domain-specific languages (DSLs), not necessarily Lisp-like, that can be combined together to let a program be expressed in the “most appropriate” form for the problem. This books is structured around exactly this idea, defining a succession of languages of increasing sophistication to demonstrate Racket’s capabilities. Each language has a “reader” to convert strings into Lisp S-expressions and an “expander” to translate these forms into Racket (or another Racket-defined language). The examples include a simple stack-based calculator, an embedded JSON parser, and a Basic interpreter that exercises the power of a full parser.

The description of Racket itself is very thorough, to the extent that this book could be treated as a language definition itself, complementing the article about it. Certainly it provides the most accessible way to learn the language and explore what it means to explore “language-oriented” programming.

It’s possible to define DSLs in Common Lisp or Scheme without Racket’s extra functionality, and in many cases that will be sufficient – especially when the DSL is a Lisp itself. But there will certainly be applications for which Racket is absolutely the right tool.