Apertures: A Language with Holes

April 1, 2024

In the previous SICP post, I argued that building a language is the right move when a problem domain has clear compositional structure. Apertures is what happens when you take that seriously for distributed coordination.

The problem: multiple parties need to share computation structure while controlling when and where their data enters. The server has optimization expertise. The client has private data. Neither wants to send what they have to the other.

The SICP answer: build a language where this is expressible. Add one primitive — a hole — to a Lisp, and you get pausable, resumable evaluation as a natural consequence.

Primitives

Every language needs atomic elements. Apertures has the usual Lisp primitives — numbers, strings, booleans, symbols — plus one addition:

?x              ;; a hole named x
?client.x       ;; a namespaced hole (who owns it)

A hole is an unknown value. It sits in an expression and says: someone will fill this in later, but not yet.

$ aperture eval "(+ 3 5)"
8

$ aperture eval "((lambda (x) (* x x)) 5)"
25

So far, standard Lisp. The interesting part is what happens when holes appear.

Combination That Tolerates Unknowns

In most languages, an unknown value is an error. You cannot add 3 to something that does not exist yet. Apertures handles this through partial evaluation: evaluate what you can, preserve what you cannot.

$ aperture partial template.apt    # contains: (+ 3 ?x 5)
(+ 8 ?x)

The evaluator added 3 and 5, but left ?x alone. The result is still an expression — not a value, not an error. This is the key move. Partial evaluation is combination that tolerates unknowns.

It goes further. Algebraic rules apply even with holes present:

$ aperture partial zero.apt        # contains: (* 0 ?anything)
0

$ aperture partial identity.apt    # contains: (* 1 ?x)
?x

$ aperture partial branch.apt      # contains: (if true ?x ?y)
?x

Zero times anything is zero, even if you don’t know what “anything” is. One times ?x is just ?x. If the predicate is known, the branch is eliminated even when the branches contain holes.

These simplifications are useful. They also leak information — if (* ?secret ?known) reduces to 0, an observer knows the secret is zero. More on that later.

Read More