Planet Scheme

Tuesday, November 18, 2025

Retropikzel's blog

Saturday, November 15, 2025

Idiomdrottning

Emacs Font Fun

I actually love fonts! (Which might surprise those who know that I leave my webpage set to the browser default font, but it’s * I love good fonts so much that I think fonts should be a reader decision, not a server decision.) When my desktop went headless after having to move to a way smaller apartment and my Emacs had to live in SSH only, I was sad because I had set up all kinds of weird fonts in Emacs and functions to switch the entire Emacs over along with “only switch this particular buffer over” variants. E.g.

(defun to-june ()
  (interactive)
  (set-frame-font "Junicode-32"))

(defun to-june-b ()
   (interactive)
   (setq buffer-face-mode-face '(:family "Junicode" :height 320))
   (buffer-face-mode))

Along with this to make a changed buffer go back to the frame-wide font:

(defun unbufface ()
  (interactive)
  (buffer-face-mode -1))

And now I have all those fun fonts working again on the Android version of Emacs! ♥︎♥︎♥︎

Hence the huge sizes. I was on 12 for most non-Junicode fonts with a -16 “big” option, while Junicode with its lower x-heights I had at 14 pts. But on “Moria”, I use 28 as the “small” size, 34 as the big size and Junicode gets to be 32.

My default font for coding and general emacsing around was (CW non-free) Futura while I wrote most prose texts with Junicode. Coding in proportional?! A geometric sans with a super ambiguous character set for I1O0? Well, it works great for Lisp for the most part and I had it set up so I could super easily toggle out from it back into Deja Vu Mono, or Fira Code these days.

(add-to-list 'default-frame-alist '(font . "Futura LT-28"))

I set this all of this up pretty soon before having to switch away from it so I only got to enjoy it for a few months so I’m grateful that I have it back albeit not on my big 21″ 4:3 screen. It’s on a more cozy and puny lantern-lit 7″ screen. Last piece of the puzzle is that I’m gonna go find a retro-ish typewritery font. Old Timey Code maybe. I was on “Bohemian Typewriter” but I couldn’t get that to work on since the Android version of Emacs don’t support OTF. And good riddance since the latin-1 coverage was so bad, but also not good riddance since the alternatives I’ve found so far are more consistenly x-spaced instead of nostalgically jittering around like an X-File on uppers.

I really like switching fonts with the same text open. It helps me get fresh eyes on the same text and see different problems or things I can write in a more beautiful or clear way.

by Idiomdrottning (sandra.snan@idiomdrottning.org) at Saturday, November 15, 2025

crumbles.blog

Tour of a pattern matcher: core patterns and the AST

Alright! Last time we looked at the mouth of extensible-match and how it chews up patterns in order to make them digestible. Before we take a look into the stomach,1 we should look at what that chewed-up form of patterns looks like. This is an episode with a bit less how and a lot more what than the last one. I’ll explain and justify the core pattern structure we just got out of the expansion process, how and why it’s different from the AST that we eventually like to work on, but also one further transformation on that AST that makes it easier for the rest of the system to digest.

We’re still in the (extensible-match match) library, which we’ll keep dipping back up into because it contains this long chain of procedure calls which actually co-ordinates the whole of the rest of the process of compilation.

Subject variables vs pattern variables

The idea of a pattern variable is simple, and is a concept exposed to the user. In a pattern like (cons x '()), x is a pattern variable. This pattern deconstructs a pair which could previously have been constructed by the same code used as an expression: the expression form takes a variable and makes it the only element of a 1-list; the pattern form takes a 1-list and puts its element in a variable. This is the essence of the equational reasoning property which makes pattern matching such a nice style to work with and think in.

Subject variables are pattern variables’ twin from the dark side. In the above example, there’ll be three subject variables: one for the pair itself; one for its car, and one for its cdr. The upshot is that the pattern matcher doesn’t generate code like (if (equal? (cdr p) '()) ...); it will always extract the thing it’s interested in to a variable first: (let ((g1234 (cdr p))) (if (equal? g1234 '()) ...)). This simplifies things because it means that the kind of thing we’re testing against is always a simple variable expression no matter what the nesting level of patterns is.

(Obiter dictum: this doesn’t affect the efficiency of the final generated code; it anticipates the Scheme compiler’s own pass which will un-nest expressions, namely CPS or ANF conversion.)

Subject variables are always generated identifiers; their precise names are only interesting to the internals of the pattern matcher. I mention them here because they’re important for understanding the structure of expanded patterns and the first few stages of compilation we’ll look at today. The first thing that %core-match-lambda does to patterns, in fact, is to generate subject variables for each of the values it will be examining. (Recall that %core-match-lambda has multiple sets of patterns, but each such set has to match the same number of values. The values for all clauses get the same subject variables.) Although these subject variables are the entry-point for the pattern match, this is in fact the last set of new subject variables generated, because all of the others (in the example above, the ones for the car and cdr of the pair) are generated during expansion.

Core patterns

Behold, a grammar:

⟨core pattern⟩ ::= (core:wildcard)
| (core:var ⟨identifier⟩)
| (core:quote ⟨datum⟩)
| (core:and ⟨core pattern1⟩ ⟨core pattern2)
| (core:or ⟨core pattern1⟩ ⟨core pattern2)
| (core:not ⟨core pattern⟩)
| (core:row ⟨core pattern⟩ …)
| (core:? ⟨expression⟩)
| (core:apply ⟨expression⟩ (⟨identifier⟩ …) ⟨core pattern⟩)
| (core:subject ⟨identifier⟩ ⟨core pattern⟩)
| (core:seq ⟨don’t worry about it⟩)

I won’t dwell on the first six rows (core:wildcard, core:var, core:quote, core:and, core:or, core:not) because they’re not very interesting. Core:wildcard is spelled _ in the high-level pattern language, core:var is just written as a variable name; core:and and core:or are now restricted to two subpatterns each. Otherwise, you can pretty much find everything you need to know about these from the documentation of the high-level forms in SRFI 262.

Core:row is where things start to get slightly more interesting. This pattern isn’t directly exposed to high-level forms, but it’s used for the patterns which can come after the procedure expression in a high-level ? pattern and the value patterns of a => pattern. They’re also used for each of the individual values at the top-level of a multiple-pattern %core-match-lambda form.

In a row pattern, all the subpatterns have to match, but the compiler is allowed to test them in any order. While a full rationale will have to wait until the next episode, in general the order in which it makes most sense to test subpatterns is not always the left-to-right order in which they appear in the source code. In the (cons x '()) example, we’re far more interested in the cdr of this pair than we are in the car, because the cdr can tell us whether the pattern matches or not; only once we know that the cdr is the empty list should we bother trying to load the car.

Core:apply corresponds directly to the high-level => pattern (which was called apply in earlier drafts of the SRFI) but has a different structure, to do with the need to give each value its own subject variable. The ⟨expression⟩ gives the procedure which is to be applied to the pattern’s subject; the ⟨identifier⟩s are subject variables to which each respective returned value will be bound; finally, the ⟨core pattern⟩ will be matched in an environment which includes those subject variables.

Core:subject is the last piece of the puzzle; it simply says which subject variable names the subject of its own subpattern.

How this all fits together is probably best illustrated by example. Our (cons x '()) example expands, within the high-level pattern matching language, to this:

(? pair?
   (=> car x)
   (=> cdr '()))

which in core patterns expands to this:

(core:and (core:? pair?)
          (core:row
           (core:apply car (g1) (core:row (core:subject g1 (core:var x))))
           (core:apply cdr (g2) (core:row (core:subject g2 (core:quote ()))))))

A single-subpattern core:row isn’t very useful, but an equivalent expansion using SRFI 1’s car+cdr2 would be:

(? pair?
   (=> car+cdr x '()))

which in core patterns is:

(core:and (core:? pair?)
          (core:row
           (core:apply car+cdr (g1 g2)
            (core:row
             (core:subject g1 (core:var x))
             (core:subject g2 (core:quote ()))))))

Something to note here is what core:var actually, formally does: it takes its subject variable and turns it into a pattern variable. g1 is a hidden, generated variable; x is bound within its clause to the exact same value.

That’s more or less it for the core pattern language; I hope it’s more-or-less clear how it works. I’ve omitted core:seq from this description. If this series ever covers core:seq and the implementation of the pattern matcher’s functionality for running simple regular expressions over sequences of Scheme values, it will be in a bonus episode, possibly quite a bit later. I’m leaving it out here because the implementation is still an unhappy mess, much more so than any other part; also, Wolfgang Corcoran-Mathe has pointed out that the current API mixes idioms in a confusing way, and fixing that might involve some other deep changes to how it’s implemented.

The Abstract Syntax Tree (AST)

As covered in a previous missive, core patterns are pseudo-record syntax objects; accessing fields from such syntax objects is slow; real records are much faster; so to make decision tree generation go faster, there’s an additional layer, called the abstract syntax tree, implemented as real Scheme records.

Actually, though, there’s (at least) one more reason besides speed that the AST is better to work with going forward – see if you can spot it in the definition:

(define-record-type primitive-pattern
  (fields (immutable expr-table pattern-expr-table)))
(define-record-type pattern
  (fields subject)
  (parent primitive-pattern))
(define-record-type wildcard-pattern
  (parent pattern))
(define-record-type var-pattern
  (fields name)
  (parent pattern))
(define-record-type quote-pattern
  (fields datum)
  (parent pattern))
(define-record-type and-pattern
  (fields subpat_1 subpat_2)
  (parent primitive-pattern))
(define-record-type or-pattern
  (fields subpat_1 subpat_2)
  (parent primitive-pattern))
(define-record-type row-pattern
  (fields subpats)
  (parent primitive-pattern))
(define-record-type not-pattern
  (fields subpat)
  (parent primitive-pattern))
(define-record-type ?-pattern
  (fields predicate-id)
  (parent pattern))
(define-record-type apply-pattern
  (fields procedure-id vars subpat)
  (parent pattern))

I’ve omitted the AST equivalent of core:seq for simplicity.

There are a couple of improvements here. I was so determined to kill free-identifier=? from the decision tree generator entirely that the expressions naming the procedures for ? and =>/apply patterns are now interned into a table, giving them each a unique integer for the decision tree generator to use as a proxy for whether they are the same identifier. (This was probably a premature optimization, in hindsight.)

But the big improvement is in the (not very well-named) distinction between a pattern and a primitive-pattern. Notice that core:subject is gone from the AST! In its place, every pattern that needs to know the name of its subject variable has it stored directly within it.

In the core pattern language, you don’t actually know what (core:quote ()) means without looking for a lexically enclosing core:subject: in our (cons x '()) example, (core:quote ()) alone could be looking at the pair (no match), the car (not intended, but might incorrectly match), or the cdr (as intended).

Making this change made the decision tree generator much simpler, apart from the speed boost from using records instead of syntax objects.3 It no longer has to track a lexically implicit subject for each pattern.

You might wonder why the code doesn’t do this already during pattern expansion, and why core patterns don’t include their subjects in the same way the AST does. Well, maybe ideally it would do this, but in the sense of having everything in its right place at the right time, it’s probably the right thing for simplicity. Doing it this way lets the expansion of patterns happen completely independently of any context; we add that context to each individual subpattern later.

Tracking just the state of each expansion in continuation-passing style is tricky enough; adding the current lexical subject variable as extra state to pass around would make it more complicated. Moreover, continuation-passing expansions imply that the expander’s final result has to be a syntax object anyway – it’s not safe to go straight from unexpanded patterns to AST records – so let’s take the opportunity afforded by the need to have a slightly redundant layer in order to at least make the process of creating that redundant layer easier. That said, if Scheme does one day grow a way to call a transformer and apply marks independently of the expander’s main loop, it will probably then be time to get rid of core patterns and track the current subject variable implicitly while expanding directly into AST records.4

The other half of the clause

We’re not quite done with the AST. We’ve talked a lot about the left-hand side of each pattern matching clause – the pattern – and we’ll continue to do so. But there is another side, which is what happens when the pattern of a clause is known to have matched.

(define-record-type action
  (fields procedure args))
(define failure-action (make-action #'fail '()))

(define-record-type patact
  (fields pattern action))

There’s not too much to say here. When a top-level pattern matches, its corresponding action is called. It’s a procedure, named by a generated identifier for each clause, plus a list of the variables bound by the corresponding pattern (which become arguments of the procedure). If no pattern matches, we call a special action called fail which just raises an exception.

A pattern and an action together make a clause, but here we call it a patact.

Unifying subject variables

That’s all the what; let’s finish up with a quick look at another bit of how. We’ll talk a lot more about what it takes to generate optimal code for a pattern match next time, but there’s one step that takes place to get the patterns in a form that’s easier to optimize.

Recall that each (sub)pattern expansion happens independently, without knowledge of the context from other expansions; and also that the expansion of => patterns into core:apply patterns. What this means is that equivalent applications generate distinct subject variables; we really want equivalent applications to create equivalent subject variables!

To see how, let’s put the example we’ve used so far into context:

(define (last ls) ; returns the last element in a list
  (match ls
    ((cons x '())  x)
    ((cons _ ls*)  (last ls*))))

These two patterns will expand, respectively, into the same core pattern we saw above and another very similar one:

(core:subject ls
 (core:and (core:? pair?)
           (core:row
            (core:apply car (g1) (core:row (core:subject g1 (core:var x))))
            (core:apply cdr (g2) (core:row (core:subject g2 (core:quote ())))))))
;; action: (g3 x)

(core:subject ls
 (core:and (core:? pair?)
           (core:row
            (core:apply car (g4) (core:row (core:subject g4 (core:wildcard))))
            (core:apply cdr (g5) (core:row (core:subject g5 (core:var ls*)))))))
;; action: (g6 ls*)

We applied car and cdr to ls in each pattern, but the result was called g1 and g2 in the first pattern and g4 and g5 in the second one. We only want to call each procedure once – if at all – in the generated code, so it would be nice to only generate one variable. This is what the pattern subject unification pass does: for every application of the same procedure to the same subject, the created subject variables should have the same name. The result is that the first pattern will still look the same, but the second will be (the AST-level equivalent of) this:

(core:subject ls
 (core:and (core:? pair?)
           (core:row
            (core:apply car (g1) (core:row (core:subject g1 (core:wildcard))))
            (core:apply cdr (g2) (core:row (core:subject g2 (core:var ls*)))))))
;; action: (g6 ls*)

It does this in a recursive pass which looks for combinations of subject, procedure, and number of output values5 it’s not seen before; when it finds one, it puts the variable names that were generated for that pattern’s version into a hash table. When it sees the same combination of subject, procedure, and co-arity again, it replaces that AST apply pattern with a version whose subject variables are renamed, and recursively traverses the apply pattern’s subpattern to change instances of those subject variables appearing within quote-patterns, var-patterns, other apply-patterns etc. to the unified name.

This is also something that could be done during expansion, but it’s just simpler to do it as an extra pass after expansion because it makes expansion independent of external context.

Potential future AST transformations

Subject variable unification is the currently the only optimization pass that’s done on the AST of the patterns before handing over to the decision tree generator, which is responsible for most of the work of finding an efficient way to match a set of patterns. But there are a couple of other passes the pattern match compiler could make at this point to improve the quality of generated code, or make the decision tree generator simpler.

Rewrite complex not patterns

At the moment, using a not pattern will completely frustrate the optimizer. It’s not too bad if you only put not around a datum or a predicate – but if you put it around an (implicit) and, the optimizer will only know how to compile it and its entire subpattern at once, and be unable to avoid potentially repeating equivalent computations for multiple similar patterns. Being able to mix in patterns which do match when they don’t match anywhere arbitrarily would just add too much complexity to the decision tree generator.

Fortunately, in 1846, compiler engineer Augustus De Morgan formalized an intuition about the relationship between negations, conjunctions, and disjunctions, which could eventually be applied here to ensure that the not pattern never appears outside of an and or or pattern. Ensuring it also never appears outside of row would be ideal, but the obvious solution – turning e.g. (not (row pat1 pat2)) into (or (not pat1) (not pat2)) – may be an accidental pessimization because or has an order-of-evaluation restriction which row doesn’t.

This AST transformation alone would be an improvement for the decision tree generator, but the real improvement would come with recognizing common not operations and being able to fully optimize them. Another benefit of this transformation would be the ability, in theory, to (sometimes) issue compile-time warnings about patterns like (not _) which will never match.

Remove quote patterns from the AST

A (core:quote val) pattern can be rewritten as (core:? (lambda (x) (equal? x 'val))), but I don’t do this during expansion because it makes it harder to recognize that two quote patterns are equivalent. Since conversion from core patterns to the AST includes the recognition of equivalent expressions in order to give them unique integer IDs, it could generate an appropriate lambda expression for each mentioned datum then.

This would be good to do together with the previous one, because adding support for explicitly recognizing not patterns in the decision tree generator would add complexity there; removing quote patterns from the AST would remove some related complexity to balance that out. The cp0-equivalent pass of the Scheme compiler will then get rid of the lambda expression again.

Re-chain and patterns to only be nested on the right-hand side

Another way to frustrate the optimizer at the moment is to write something like (and (and a b) c) instead of (and a (and b c)). The optimizer will look for things it can do in the left-hand side of an and, but if it sees another and it isn’t smart enough to be able to look again at that and’s left-hand side, mostly because reconstructing the leftwardly-nested and after deciding to take action on its contents would be too much of a pain. The ordering relationship for these two nesting structures is the same, so an AST transformation should rewrite them all into a form that’s friendlier for the decision tree generator.

Or patterns, on the other hand, don’t have this problem, because the decision tree generator will flatten those out into entirely separate patterns while it’s working, and that process can handle any nesting structure it finds.

Next time

The decision tree generator! This will probably be the third of a four-part series, before the final part – a short coda on turning decision trees into executable code.


  1. I would try to promise that, like Knuth, I will have the decorum to not continue the digestive metaphor beyond the stomach, but I fear I would be promising too much.↩︎

  2. Which is the real procedural codata counterpart to the data cons.↩︎

  3. The old, slow version of the decision tree generator had a pretty awful procedure whose job was to push core:subject patterns down the core pattern tree lazily.↩︎

  4. Sheesh, that one missing expander feature alone would probably let me get rid of about a quarter of the lines of implementation in extensible-match.↩︎

  5. For very sad reasons explained in the SRFI, you can’t use a => pattern to match on the number of values a procedure returns. It’s nonetheless possible to construct valid, non-erroring patterns where the same procedure applied to the same subject would return different numbers of values and match different numbers of patterns. Writing one is an exercise for the reader.↩︎

Saturday, November 15, 2025

Sunday, November 9, 2025

crumbles.blog

Tour of a pattern matcher: expression and pattern expansion

I’ve posted a lot on Mastodon (and a little bit here) about the adventures I’ve had implementing an extensible pattern matcher in Scheme. Well, the SRFI isn’t too far off finalization now, and the implementation is unlikely to change dramatically, so I reckoned it might be worthwhile to take readers on a tour of how the implementation works: for one thing, for those who want to be able to dive in and understand the code in future and maybe hack on it; for another, as a further propaganda piece in favour of a style of programming still under-used in Scheme; but also I think there’s a lot the budding Schemer can learn about Scheme, and a lot the budding functional programmer can learn about this central construct of functional languages, from studying the implementation. There’s still a lot of mess in the code that I’m moderately embarassed about (we’ll deal with one such embarassment today, albeit a less immediately avoidable one). But I hope that will get cleaned up over time; like visiting a newly-furnished building where they haven’t yet discovered that putting that cabinet there will make the door handle hit the wall and damage the wallpaper, you can see the idea of the place, and those little things will get sorted eventually.

As mentioned last time I talked about the implementation, extensible-match is a Scheme macro complex enough that it can be seen as a compiler unto itself. There’s a sense in which this is a truism – every Scheme macro is a compiler from a dialect of Scheme containing that macro’s syntactic extension into a dialect that doesn’t use it, at least locally – and attempting to define what makes a macro a compiler beyond that is somewhat impressionistic. But extensible-match has intermediate representations, multiple passes, an optimizer, and a code generator to turn all of that back into Scheme code: it’s definitely on the side of a compiler, taken as a whole. But today, following the path that your patterns actually take when the compiler digests them and turns them into running Scheme code, we’ll just take a look at the front end, which looks more like a traditional Scheme macro definition. Even here, there are a few things worth pointing out along the way!

The match forms

We start our journey in the uncreatively named (extensible-match match) library, which provides all the Scheme expression forms with which pattern match programmers actually use to interact with the pattern matcher. There are a dozen distinct macros for getting access to the pattern matcher – from the very basic match itself (which will probably consistute about 90% of real-world uses) to the somewhat obscure match-letrec*.

Under the hood, though, all of them ultimately expand into a use of match-lambda. Partly this is for party reasons – one thing to name them all, one thing to define them and so on – but also match-lambda is, in fact, the fundamental pattern matching form, in that it offers almost everything that all of the subordinate pattern matching forms need. Specifically, unlike match it handles multiple values without consing them up into a list first; unlike match-let and friends it has multiple clauses which it tries in succession.

Match-lambda in turn is implemented in terms of case-lambda, the Scheme way of making a procedure which takes varying number of arguments by simply dispatching to different code based on the number of them. Case-lambda is built in to Scheme, so the first bit of pattern matching – discerning what to do based on the number of values given – is done for us. All match-lambda does is group the pattern matching clauses (which of course can discern more finely) by the number of values they expect, creates a case-lambda clause for each of those groups, and puts a %match-lambda inside each of those clauses. This %match-lambda handles the next stage: it still handles multiple clauses, but each of them expect the same number of values.

This is a good moment to take a short diversion into macro efficiency, or rather into deliberate inattention to efficiency. An ideal macro expansion will look different depending on the exact forms it receives. In this case, you might think it would somehow optimize the macro if it treated the (common!) case where every match-lambda clause takes the same number of values specially, and expanded directly into a single lambda instead of a case-lambda with only one clause, which itself is going to turn into a call to a lambda expression once this %match-lambda is expanded. Not so!

When writing Scheme macros, it’s always better to just handle the general case and let the compiler itself deal with the redundant code you might generate in special cases. This is the essence of the Guaranteed-Optimization Clause of the Macro-Writer’s Bill of Rights. A case-lambda with only one clause is usually treated by the expander as identical to a lambda even before any optimization begins; resulting structures like (lambda (x y z) ((lambda (x* y* z*) ...) x y z)) are trivial for the compiler itself to remove and turn into a more sane form. Adding it into the macro implementation directly would add more lines of code, but would bring no benefit at all the moment the expansion got out of the expander: in a Scheme compiler, getting rid of pointless make-work code like this is usually the first pass done after macro expansion, a combined inlining/partial evaluation/constant folding pass which Chez Scheme calls ‘cp0’ and Guile calls ‘peval’.1 Compiler optimizations which, in a language without macros, seem fairly pointless – ‘who’d ever write code like that?’ – become valuable with macros, and become more valuable the more powerful the macro system is – ‘oh, code generated by other code looks like that!’

This is the point of the ‘guaranteed-optimization clause’: it lets us be laudably lazy as macro authors by guaranteeing us that our code will still run fast even if we don’t take the time to deal with redundant expressions; and it lets the users of macros know that they don’t have to worry about the performance impact of using a higher-level syntactic construct. Macros generate all sorts of silly code structures, not only because their authors don’t bother to deal with special cases, but also because there’s a lot that macros don’t know about the context they’re being used in. But that information which is trivially accessible for the Scheme optimizer once it has a fully-expanded abstract syntax tree. An example of not needing to worry about special cases is that, even in performance-sensitive code, if your macro takes in an expression but only wants to evaluate it once, you might be tempted to try to recognize if the expression is a single identifier to avoid redundantly generating a let expression giving it a name local to your macro. But you don’t need to: the Scheme compiler can just take care of it! An example which isn’t just laudable laziness is when the expansion of a macro includes code which treats its input specially if it’s a certain type – but the Scheme compiler with the fully-expanded program at hand can run a type propagation pass and change the code to reflect its knowledge of the possible types a variable might have at the specific point of each individual use of the macro.

We’ll see a few more examples of this philosophy of ‘just let the Scheme compiler deal with it later’ throughout our tour. In general: ask not what you can do for your optimizer, ask what your optimizer can do for you! The expand/optimize procedure in Chez and Loko, or the ,optimize REPL command in Guile, is your friend whenever you’re trying to work out what special cases it’s worth writing extra code to deal with vs. which would be wasted effort on something the compiler itself could do better anyway.

Anyway, %match-lambda turns out not to be the end of the chain of delegating to yet more primitive forms called lambda, although this final one doesn’t generate a redundant expression in the final code. This is an extensible pattern matching library, so patterns can be forms that the matcher itself doesn’t natively know how to interpret. In order to deal with those forms, we have to expand them into forms which it can natively deal with. That’s right, it’s a macro expander within a macro! (Kind of!)

Pattern expansion

So we move on to the (extensible-match expand) library, which contains the expand-pattern form which %match-lambda delegates to. The job of this library is to turn unexpanded patterns, containing all the weird and wonderful pattern syntax that users defined themselves, into ‘core patterns’ that the rest of the pattern match compiler can deal with directly. We’ll deal with the structure and rationale of core patterns in the next installment; for now, if you’re familiar with the structure of SRFI 262 patterns, they should look pretty familiar with an extra core: at the beginning of their name. For disambiguation, variables are now spelled (core:var name-of-the-var) and _ is spelled (core:wildcard), but they’re generally pretty similar in structure.

The interpretation of non-core patterns is actually done by yet another library, (extensible-match pattern-syntax), but this was a fairly late change which I made in order to be able to write an implementation-specific version of only that functionality for Guile, which doesn’t yet support identifier properties. Potentially in future, implementation-specific versions could appear for other Schemes which provide enough access to their internals to allow correct (or nearly correct) pattern syntax semantics to be impemented in some way other than actually using SRFI 213. Anyway, the two libraries still work together pretty much as one.

The forms in these two libraries are, surprisingly, themselves macros and not procedures, and they’re used in a bit of an unusual way. The problem is applying macro hygiene to the pattern syntax expansions. Put (very) roughly, macro hygiene works by the expander creating a new, unique token every time one expansion of one macro use takes place; when the user-written code which writes the expansion has returned, the expander goes in and changes the names of all the identifiers in the returned code by adding that magic token.

Well, extensible-match itself is a macro, and doesn’t have a way to create one of those magic tokens to apply to identifiers. We have to let the macro expander of the Scheme implementation itself do it for us – but there’s no standard Scheme API for that. What do?

The answer is macros in continuation-passing style. Hilsdale and Friedman’s paper, linked, is a little obscure and doesn’t really make a compelling case for using this in practice apart from hinting at the possibility of some really dirty tricks (Petrofsky extraction, Kiselyov defilement, etc.). It’s a well-known trick, though, for implementing Scheme macros which can expand to a language other than Scheme (embedded within Scheme). Patterns are an example of such a language; Alex Shinn’s loop macro clauses are another (Taylor R. Campbell’s version of this looping macro is more popular, but I’ve linked Shinn’s original implementation as it’s shorter and shows the continuation-passing trick more clearly).

The idea is that a continuation-passing macro matches code of the form

(the-keyword input-of-the-macro ... next-keyword . next-arguments)

and always transforms it into the form

(next-keyword output-of-the-macro . next-arguments)

So the macro receives the name of another macro which will further process its own output. Because the whole output after this transformation goes back into the expander, which thinks a complete expansion is finished, it will apply a new magic token to everything in the output-of-the-macro. If output-of-the-macro is itself a macro use in the sublanguage being expanded into, next-keyword can again recursively transform it into a continuation-passing style macro use at the Scheme level, referring back to itself as the continuation.

You can do this entirely in syntax-rules! Shinn’s loop does exactly this – macro-extensible macros don’t have to involve the most advanced expander features available. But offering direct access to an extension API based on continuation-passing macros has some disadvantages:

  1. It exposes implementation details of the sublanguage to writers of extensions to the sublanguage, failing to maintain an appropriately opaque abstraction barrier between usage and implementation. One can imagine that someone might come along and decide to actually destructure the next-arguments, thinking that they can understand what the sublanguage implementation is packing in there and re-use that information for a nifty feature. In so doing they create a Hyrumesque nightmare for the developer of the sublanguage, who will break their nifty feature the next time they decide that this internal continuation structure needs changing.

    The problem is only exacerbated by continuation-passing macro extension APIs which provide more than one continuation, or more information, to their writers. Olin Shivers’s loop macro and Taylor R. Campbell’s implementation of foof-loop both do this: the result may be powerful, but from the point of the designer of the sublanguage, it is inflexible, hemming in future extensions of the power of the sublanguage itself.

  2. It fails to establish a distinct namespace for extension keywords to safely live in, outside of which uses of those keywords are either errors (as in (chibi loop)) or have distinct but related semantics (as in the pattern matcher). It also fails to prevent unrelated keywords being accidentally, mistakenly used as keywords within the sublanguage, which can expand into unrelated forms with unexpected behaviour.

  3. It makes error reporting for incorrect uses of extensions to the sublanguage confusing, since upon failure to match any syntax-rules clause, it has no idea which parts of the macro are implementation details of the sublanguage and which are parts written in the sublanguage and directly entered by the programmer.

  4. Macro writers have to be careful to always return to the continuation they were given. (On the other hand, providing quote or quote-syntax as the continuation keyword makes it easy to debug more complex expansions by single-stepping; whereas most Scheme implementations unfortunately do not provide macroexpand-1 for forms in the Scheme language itself.)

The extensible pattern matcher side-steps all of this by capturing the transformer procedures of its extension macros directly (in identifier properties) and calling them only on the user input, then embedding the output of the transformer procedure into a continuation-passing macro. The only information the transformer for pattern syntax has access to is the syntax object representing the pattern use itself; if there’s an error, it’ll be expressed in terms of this only and not in terms of some low-level form the pattern syntax user isn’t interested it. All the pattern syntax transformer has to do is return its expansion, and what the pattern expander does with it after that is none of its business: a clean abstraction barrier, just like real Scheme macros.

You might ask why, having thus eliminated the usual reasons for writing continuation-passing macros, the pattern syntax expander isn’t just a simple procedural recursion like this:

(define (expand-pattern-syntax stx)
  (syntax-case stx (core-pattern)
    ;; Base case, a fully expanded pattern identified by some special head:
    ((core-pattern expansion) #'expansion)
    ;; Recursive case of a pattern with a pattern syntax keyword:
    ((keyword . _)
     (expand-pattern-syntax ((look-up-pattern-transformer #'keyword) stx)))))

Indeed, Sam Tobin-Hochstadt’s paper on extensible pattern matching in Racket presents it as if it did work this way.

As mentioned above, the reason is hygiene: we want to go back into the Scheme expander after each step and apply a unique magic token to any inserted identifiers for each individual use of pattern syntax, so that the names don’t collide with one another. This is, admittedly, somewhat pedantic: there’s no real point to introducing identifiers with the current structure of patterns, but with future extensions there might be, and it’s possible that some unusual pattern macro implementation might have a reason to do it anyway – so it’s worth taking the effort to do the right thing.

All of (extensible-match expand) is thus concerned with implementing a recursive expansion of pattern syntax while maintaining this hygienic property, which it has to do in continuation-passing style. For example, expanding a core:and or core:or means each of their sides have to be expanded recursively, then those expanded forms placed back in a new core:and or core:or which is then deemed fully expanded. If the core:and or core:or is actually a subpattern of some larger pattern – another core:and or core:or even (the core versions of these forms can only be two-armed, as a simplification) – they then have to pass this fully-expanded form back to the ongoing expansion of that larger pattern, so that it in turn can continue and reconstruct its input.

Tobin-Hochstadt’s presentation in his paper is actually something of a pedagogical lie, although it’s not much of one since Racket does have a way to apply its expander’s magic tokens without going all the way back into the expander, and Racket’s pattern matcher uses that to avoid this continuation-passing macro malarkey. This is therefore all a bit embarassing for a Schemer who can look across the aisle and see that Racket has always had better ways of doing this; hopefully, one day, Scheme will too, and all this continuation-passing nonsense will be gone.

As one last point, we don’t expand only one pattern at once – in match-lambda we have a number of clauses, each of which has a number of patterns (for each of the values handled by that clause). So we have not only an expand-pattern in continuation-passing style, but an expand-patterns too, which expands all the patterns in a list of patterns and passes them all on as a list of core patterns; then an expand-patternses which expands all the patterns in a list of list of patterns, passing them on as a list of lists of core patterns, which is what %core-match-lambda actually uses directly. These, of course, work by invoking expand-pattern with a continuation which keeps track of the already-expanded patterns and the ones yet to be expanded. Phew!

All of that gets bundled up and, as %match-lambda requested, the final continuation of the expansion process is %core-match-lambda, the form which co-ordinates the whole pipeline of the actual compilation of patterns into Scheme code.2 We’ll start talking about the stages %core-match-lambda puts the patterns through next time – from here on in, it’s all procedures, and no more expanding match macros into yet more macros!

One last thing: that ‘almost

There’s one wrinkle in using match-lambda for everything: the forms which do recursive binding – match-letrec, match-letrec*, and match-define – practically speaking need access to something that match-lambda doesn’t give them: they need to know the names of all the variables bound by the pattern match. In order to know that, they have to expand the patterns and extract the list of variables before match-lambda does.

Racket actually expands the patterns twice for this functionality, but I think this is unnecessarily nondeterministic. There is no guarantee that any syntax transformation has to be a pure function; while every expansion of a macro should have the same effect, if you throw in generated identifiers and the possibility of seeded hash functions used in a macro implementation and similar such things, there’s no guarantee that every expansion will be detectably identical. So my view is that it’s better to expand the patterns only once. Match-letrec and match-define therefore invoke expand-patterns or expand-pattern themselves, skip around the publically-exposed match-lambda, and go directly to %core-match-lambda. Match-letrec* is implemented in terms of match-define, letting the Scheme implementation take over the implementation of letrec* semantics.

Next time

I’m not sure exactly how much it will make sense to cover in each individual entry in this series, and thus also not sure how many entries there’ll be. But next time we’ll certainly look at the structure of core patterns and the abstract syntax tree, and maybe start to look at the real meat in the decision tree generator.


Greetings, reader from the future! You can read the next part here.


  1. Most compiler courses: ‘Here is 90% on parsing and a tiny bit on register allocation; good luck and have fun!’

    Kent Dybvig: calls the pass of his compiler which comes right after parsing and macro expansion ‘Compiler Pass Zero’, because parsing and expansion aren’t really compiling.↩︎

  2. That’s right – just like Chez Scheme, the architecture of extensible-match is such that it doesn’t really consider expansion per se to be compilation.↩︎

Sunday, November 9, 2025

Tuesday, November 4, 2025

Mark Damon Hughes

Phlog Collection 2025-11-04

I haven't set up an automated way to push my Gopher phlogs from Cyberhole Online up here yet, so I'm just going to manually paste for the moment. So this may be a bit random.

mdh@Aegura:~/Code/CodeJS/Cyberhole/gopher/hole/phlog% cat `ls -1 *.txt|sort -r`|pbcopy

i=== Digital Mark's Phlog ===
2025-11-04 19:06:46 UTC
Momentary Thoughts About Game Design

I've been making games for 46-ish years, and I still don't know how to
make a "compelling narrative". Sometimes I have good gameplay and "kill
the thing" plots.

A few times I've worked at studios and had a writer who could produce
lore text, but we still didn't have much story.

That's why my games are mostly interface first, what does the technology
let me do, then work out some game logic behind it.

I generally reject the narrativist view of games. Games are interaction
+ challenge = fun. Books and movies are characters + plot = fun. Visual
novels combine book style with illustrations and a homeopathic level of
interaction, maybe one choice every 10-20 minutes for most. Modern MMOs
& mobage are largely predefined characters on a rails plot, with a
shooting or action game in between. Designers for these hate the idea
of you having your own character or making plot choices.

What I find interesting is exploring a world, fighting, and building. So
Atari 2600 games are still fascinating; the absolute focus on gameplay
was required by the limitations of the machine. Luanti (and that "mines
craft" game that's like it) is endlessly fun. Juice Galaxy is amazing,
just weird blobby monsters, toys, and vehicles in procgen world. AAA
titles are basically all crap, they have characters I can't stand, doing
things I don't want to do, minutes of fun after hours of hitting X.

Text adventures are in a weird space, where there's varying levels of
exploration or plot railroad, varying levels of character predefinition.
Hitchhiker's Guide to the Galaxy is fun once (possibly frustrating for
months), but it's very linear and you are Arthur Dent, of the late
Earth. Zork and Colossal Cave Adventure are still fun, because there's
no fixed path to solve everything, and you're not defined by the game.

A lot of people do like the excruciatingly long cutscenes and "Press F
to Pay Respects" pseudo-gameplay, I'm well aware I'm an outlier. But
that's why I make my games for me. If someone else has fun, too, that's
a sweet bonus!

2025-10-31 05:10:56 UTC
Cyber Mudhole

Starting on the Lisp Game Jam Autumn 2025
https://itch.io/jam/autumn-lisp-game-jam-2025

Plan is to put up a Terminal that talks to a MUD written in my Kawa
servlet platform.

I wrote a long, mostly aspirational, list of MUD commands inspired by
LambdaMoo and TinyMUD; it's not very like my old combat-oriented
Circle MUD. I doubt I'll get more than a fraction of the commands
done, but if multiple players can move around and say and emote, it's
good enough.

The way this works is there's a Java servlet container, and a single
servlet that takes POST requests for commands, then returns all your
chat history since last call. If it polls every so often with empty
command, it should look real-time-ish, and connection keep-alive
should prevent it from hammering the server.

The servlets are in Kawa Scheme, which compiles to normal enough Java
classes. I'll put the whole framework up on gitlab or whatever as I
get towards the end, it's definitely changing a lot each update right
now.

2025-09-02 13:03:13 UTC
Phlogging Like a Typewriter

One thing that occurs to me when writing gopher phlogs, is the 70-
column limit feels like typing on paper. Get near the end and I have
to guess if the next word will fit or not, and whack the carriage
return DING all the way back. I haven't got in the habit of hyphen-
ating words but I did it right there!

Is the physical act of typing different when you don't have auto
word wrap on? I could vi :set tw=70, but I don't.

2025-09-02 12:32:03 UTC
Mystic Dungeon beta

I have a barely-working version of the Mystic Dungeon, my dungeon
exploration game. Everything in the game system works again,
and I've added three new stores (one you won't find for a while!),
and some secrets to find outside of town.

Right now messaging is incredibly spammy and ugly, using up the full
screen if you move fast. Just wait a few seconds and the messages
will dissipate. I'll fix those in the next pass.

You can save [F3] and load [F2] and it's saved to local storage.

https://cyberhole.online/dungeon

Have fun, and let me know what you think!

2025-08-18 08:34:55 UTC
Amazing (converting MysticDungeon games)

I've now managed to convert one of the MysticDungeon.club games, see
front page of https://cyberhole.online/

That wasn't too hard. It's using like 1/2 of the code of the previous
version, and doesn't rely on Node or have to be built, I can just empty
cache and reload a page. But it's a little harder to debug, eslint
doesn't work well on pile-o-random-js.

Amazing's not that impressive, and I'll probably do LostTreasure and
some other small ones, then get the big stuff converted. PortalWorlds
and Dungeon both rely on a shitload of libraries.

The high score server's gone, which is unfortunate. There's a few
options to replace it. A server running Scheme, of course. Or maybe some
web service that I can ajax out to? Cross-site posting is complicated
now.

2025-08-01 03:30:30 UTC
TinyBasicWeb

Now I have a working boot-to-READY prompt BASIC!
It's pretty primitive still, just enough to do interactive
forms, and I plan to put in some web tech (REMOTE and REDIR)
commands which will make it useful for that.

Currently some BASIC microcomputer games will work in it.

So with that basically (o_o) functional, I can focus on
adding more services to the CYBER HOLE.

2025-07-23 02:06:47 UTC
Gopher Holed

And now the CYBER HOLE has my phlog! I can either log in and
write-phlog.zsh, or do it locally and scp into place, refresh and
it's rebuilt by script.

Just finished the Lispy Gopher Show
gopher://gopher.club/1/users/screwtape
(who hasn't been updating his phlog, but like I'm Mr Reliability?)

RIP Ozzy Osbourne. The best and drunkest of us.

2025-04-27 14:39:03 UTC
Dayglo Decade

I was listening to Children of the CPU
https://childrenofthecpu.bandcamp.com

While testing out my write-phlog script.

2025-04-27 13:47:08 UTC
First post

So driven by my Hypertext post
https://mdhughes.tech/2025/04/22/some-of-my-history-of-hypertext/

I needed to set up Gopher again, and not just on SDF or tilde.town.
Talking to Screwtape & CatK got me moving on it.

Currently I'm running Motsognir,
https://motsognir.sourceforge.net
since it works well on BSD; on MacOS, I had to comment out the "dot
feature" to make it compile, but I changed NoTxtPeriod=1, the default is
just wrong, so it's fine.

Which has made it pretty easy to set up CGI (Common Gateway Interface!)
scripts to generate header, footer, and phlog indexes.

And I use shell scripts to start, stop the server, and generate phlog
posts in my format (TIME.txt filename; TIME, TITLE, blank for body); I
would use RFC822 but it's a little more parsing, and the date format is
wrong.

For client, I mostly test in Lynx
https://lynx.invisible-island.net
but also Lagrange
https://github.com/skyjake/lagrange

Which has revealed a few bugs in how I write gophermaps!
In particular, Lynx doesn't care if you list a text file as a dir or
text or binary, it'll render it anyway. Lagrange is very picky, so a
dumb directory doesn't work with it.

by mdhughes at Tuesday, November 4, 2025

Thursday, October 30, 2025

Andy Wingo

wastrel, a profligate implementation of webassembly

Hey hey hey good evening! Tonight a quick note on wastrel, a new WebAssembly implementation.

a wasm-to-native compiler that goes through c

Wastrel compiles Wasm modules to standalone binaries. It does so by emitting C and then compiling that C.

Compiling Wasm to C isn’t new: Ben Smith wrote wasm2c back in the day and these days most people in this space use Bastien Müller‘s w2c2. These are great projects!

Wastrel has two or three minor differences from these projects. Let’s lead with the most important one, despite the fact that it’s as yet vaporware: Wastrel aims to support automatic memory managment via WasmGC, by embedding the Whippet garbage collection library. (For the wingolog faithful, you can think of Wastrel as a Whiffle for Wasm.) This is the whole point! But let’s come back to it.

The other differences are minor. Firstly, the CLI is more like wasmtime: instead of privileging the production of C, which you then incorporate into your project, Wastrel also compiles the C (by default), and even runs it, like wasmtime run.

Unlike wasm2c (but like w2c2), Wastrel implements WASI. Specifically, WASI 0.1, sometimes known as “WASI preview 1”. It’s nice to be able to take the wasi-sdk‘s C compiler, compile your program to a binary that uses WASI imports, and then run it directly.

In a past life, I once took a week-long sailing course on a 12-meter yacht. One thing that comes back to me often is the way the instructor would insist on taking in the bumpers immediately as we left port, that to sail with them was no muy marinero, not very seamanlike. Well one thing about Wastrel is that it emits nice C: nice in the sense that it avoids many useless temporaries. It does so with a lightweight effects analysis, in which as temporaries are produced, they record which bits of the world they depend on, in a coarse way: one bit for the contents of all global state (memories, tables, globals), and one bit for each local. When compiling an operation that writes to state, we flush all temporaries that read from that state (but only that state). It’s a small thing, and I am sure it has very little or zero impact after SROA turns locals into SSA values, but we are vessels of the divine, and it is important for vessels to be C worthy.

Finally, w2c2 at least is built in such a way that you can instantiate a module multiple times. Wastrel doesn’t do that: the Wasm instance is statically allocated, once. It’s a restriction, but that’s the use case I’m going for.

on performance

Oh buddy, who knows?!? What is real anyway? I would love to have proper perf tests, but in the meantime, I compiled coremark using my GCC on x86-64 (-02, no other options), then also compiled it with the current wasi-sdk and then ran with w2c2, wastrel, and wasmtime. I am well aware of the many pitfalls of benchmarking, and so I should not say anything because it is irresponsible to make conclusions from useless microbenchmarks. However, we’re all friends here, and I am a dude with hubris who also believes blogs are better out than in, and so I will give some small indications. Please obtain your own salt.

So on coremark, Wastrel is some 2-5% percent slower than native, and w2c2 is some 2-5% slower than that. Wasmtime is 30-40% slower than GCC. Voilà.

My conclusion is, Wastrel provides state-of-the-art performance. Like w2c2. It’s no wonder, these are simple translators that use industrial compilers underneath. But it’s neat to see that performance is close to native.

on wasi

OK this is going to sound incredibly arrogant but here it is: writing Wastrel was easy. I have worked on Wasm for a while, and on Firefox’s baseline compiler, and Wastrel is kinda like a baseline compiler in shape: it just has to avoid emitting boneheaded code, and can leave the serious work to someone else (Ion in the case of Firefox, GCC in the case of Wastrel). I just had to use the Wasm libraries I already had and make it emit some C for each instruction. It took 2 days.

WASI, though, took two and a half weeks of agony. Three reasons: One, you can be sloppy when implementing just wasm, but when you do WASI you have to implement an ABI using sticks and glue, but you have no glue, it’s all just i32. Truly excruciating, it makes you doubt everything, and I had to refactor Wastrel to use C’s meager type system to the max. (Basically, structs-as-values to avoid type confusion, but via inline functions to avoid overhead.)

Two, WASI is not huge but not tiny either. Implementing poll_oneoff is annoying. And so on. Wastrel’s WASI implementation is thin but it’s still a couple thousand lines of code.

Three, WASI is underspecified, and in practice what is “conforming” is a function of what the Rust and C toolchains produce. I used wasi-testsuite to burn down most of the issues, but it was a slog. I neglected email and important things but now things pass so it was worth it maybe? Maybe?

on wasi’s filesystem sandboxing

WASI preview 1 has this “rights” interface that associated capabilities with file descriptors. I think it was an attempt at replacing and expanding file permissions with a capabilities-oriented security approach to sandboxing, but it was only a veneer. In practice most WASI implementations effectively implement the sandbox via a permissions layer: for example the process has capabilities to access the parents of preopened directories via .., but the WASI implementation has to actively prevent this capability from leaking to the compiled module via run-time checks.

Wastrel takes a different approach, which is to use Linux’s filesystem namespaces to build a tree in which only the exposed files are accessible. No run-time checks are necessary; the system is secure by construction. He says. It’s very hard to be categorical in this domain but a true capabilities-based approach is the only way I can have any confidence in the results, and that’s what I did.

The upshot is that Wastrel is only for Linux. And honestly, if you are on MacOS or Windows, what are you doing with your life? I get that it’s important to meet users where they are but it’s just gross to build on a corporate-controlled platform.

The current versions of WASI keep a vestigial capabilities-based API, but given that the goal is to compile POSIX programs, I would prefer if wasi-filesystem leaned into the approach of WASI just having access to a filesystem instead of a small set of descriptors plus scoped openat, linkat, and so on APIs. The security properties would be the same, except with fewer bug possibilities and with a more conventional interface.

on wtf

So Wastrel is Wasm to native via C, but with an as-yet-unbuilt GC aim. Why?

This is hard to explain and I am still workshopping it.

Firstly I am annoyed at the WASI working group’s focus on shared-nothing architectures as a principle of composition. Yes, it works, but garbage collection also works; we could be building different, simpler systems if we leaned in to a more capable virtual machine. Many of the problems that WASI is currently addressing are ownership-related, and would be comprehensively avoided with automatic memory management. Nobody is really pushing for GC in this space and I would like for people to be able to build out counterfactuals to the shared-nothing orthodoxy.

Secondly there are quite a number of languages that are targetting WasmGC these days, and it would be nice for them to have a good run-time outside the browser. I know that Wasmtime is working on GC, but it needs competition :)

Finally, and selfishly, I have a GC library! I would love to spend more time on it. One way that can happen is for it to prove itself useful, and maybe a Wasm implementation is a way to do that. Could Wastrel on wasm_of_ocaml output beat ocamlopt? I don’t know but it would be worth it to find out! And I would love to get Guile programs compiled to native, and perhaps with Hoot and Whippet and Wastrel that is a possibility.

Welp, there we go, blog out, dude to bed. Hack at y’all later and wonderful wasming to you all!

by Andy Wingo at Thursday, October 30, 2025