Writing Forth with an Accent (#301)
Years ago, back when face masks were still fashionable, I remember first actively reading about Forth (Moore 2025), a teaching assistant I worked for had lended me his copy of the interview. Apart from Charles “Chuck” Moore’s language’s name, the two things that stuck with me after reading his interview were that “6 inches” could somehow be understood as an elegant expression and that dictionaries’ definitions are circular.
4 [ dup * ] #stack [ 4 [ dup * ] ] unquote . 16
It wasn’t until a couple years later when I came across one of my all-time favourite talks (Creager 2023), where I got introduced to quoting a part of the stack and pushing it as a quotation itself onto the stack, which deeply intrigued me.
[!] [ [1] [*] primrec ] define 32 ! . 263130836933693530167218012160000000
I truly have a soft spot for purely symbolic models of computation, but their simplicity is foremost on the page; quadratic-runtime interpreters such as Zpr’(h aren’t much fun when everything is sluggish irrespective of what one writes.
127 ternary [["0" "+" "-"] swap index] map concat . "++-0+"
Reverse Łukasiewicz notation always has a certain sheen to it, possibly due to its alien nature: Expressions no longer look like separate figures plonked into running text, no parentheses exist to disambiguate non-associative operations. Everything is written as if it were spoken without ever raising or lowering one’s voice: nesting is drawn from the passage of time.
[range] [ [[]] [quote ++] primrec ] define 9 range [dup *] map . [ 1 4 9 16 25 36 49 64 81 ]
But I don’t think codifying syntax is what C. Moore discovered in the late sixties. You could envisage a postfix dialect of C without much hassle, with the exact same compiler architecture, running on the exact same van Neumann machines, with the exact same uncompilable polymorphic semantics. Such endeavours surely exist (Purdy 2017) (Pestov 2008), yet stray far away from Forth, possibly only imitating its syntactic chrome.
[repsqr] [ [ [dup *] [dup * over *] ] ] define [go] [ concat 1 swap unquote nip ] define [pow] [ binary [repsqr swap index] map go ] define 2 9 pow . 2 100 pow . 512 1606938044258990275541962092341162602522202993782792835301376
After all, C. Moore appears much more interested in interpreting the compiler as a mapping from (dyed) english to machine instructions as close to the identity. (Moore 2013) Such focussed interest in the zeroth meta level is understandable given his biography as a hardware engineer; stacks don’t fit into registers and higher-order types lead to undecidably long compilation times.
[even] [ 1 - dup 2 mod * 1 + ] define [odd] [ 1 - dup 2 mod 1 swap - * 1 + ] define [!!] [ dup 2 mod [[even] [odd]] swap index [*] ++ [1] swap primrec ] define [A000165] [ 2 * !! ] define [A001147] [ 2 * 1 - !! ] define 16 A000165 . 1371195958099968000 16 A001147 . 191898783962510625
When you reject the meta level, however, your language cannot be reflexive. Opposite to S-expressions, which anecdotally first were used to describe executable M-expressions, their fusion being the bedrock of Lisp’s reflexivity, Forth descendants appear to have found their way to homoiconicity by reifying the machine-inspired syntax into a symbolic form.
[ 1000 [1] [*] primrec len ] dup . unquote . [ 1000 [ 1 ] [ * ] primrec len ] 2568
Forth isn’t symbolic. Forth isn’t structured. It is much more a language toolkit for facilitating the so-inclined to write factored code. (Moore 2025, ii) (Falvo II. 2008, ii) That doesn’t interest me that much; it lends credence to the view that Forth is nothing more than one flavour of syntax for machine code, which makes it closer to a representation than a language.
[range0] [ [[]] [1 - quote ++] primrec ] define [range] [ over - range0 swap quote [+] ++ map ] define 3 9 range . [ 3 4 5 6 7 8 ]
With the monoidal view on concatenative programs (von Thun), paired with a confluence statement about the interpretation semantics, β-equivalence feels more syntactical.
[range] [ over - swap quote [+ 1 - quote ++] ++ [[]] swap primrec ] define 3 9 range . [ 3 4 5 6 7 8 ]
Lisp’s influence on how we contemporarily conceive of programs I see everywhere, again and again. How influential Smalltalk was routinely surprises me. (Griesemer 2015, i) (Pestov 2008, i)
[repsqr] [ [ [dup * 2 pick mod] [dup * over * 2 pick mod] ] ] define [go] [ concat 1 swap unquote nip nip ] define [powmod] [ rot rot binary [repsqr swap index] map go ] define [α] [ 0xa222c173f2ede3f6005d15e0cd33ffc3 ] define [e] [ 0x1bef2300b71256a21663e35cba8d44de ] define [n] [ 0x97e368e54ce718496f0f3cdafb6375f7 ] define α e n powmod . 27441235791830587278843364744850155209
Software archival techniques, how we present our views on discrete machines to those that come after us, weighs heavy on contemporary minds who see so many things once cherished crumble before their eyes, lost to time in the blink of an eye. (Lack 2025) (Moore 2025, i) (Nguyen and Kay 2025) What Forth brings to the table here is both its definable semantics⸺compare the daunting task to archive the still evolving ISO C++ standard in a way anyone can meaningfully read it in a few centuries’ time or, still non-trivial, correctly interpreting the subtleties in the Go language specification (Griesemer 2025) to abstractly defining the concept of a word and of a stack followed by a fully boot-strappable implementation written in stack-aware words⸺as well as its affinity to factorization⸺at many levels of understanding, parts of the program can be understood in isolation and exchanged for a more fitting part without introducing global brittleness into the program. Then again, I sense these properties might be but a syntactic illusion of Forth, independently reproducible in every environment.
[x] [ "y" ] define [y] [ x ] define [x] [ "x" ] define x y ++ . "xy"
I haven’t quite warmed up to the hyperstatic global environment (Falvo II. 2008, i), but I get how you need some facility to reclaim parts of lexical scoping in the unstructured, flat world of a singular stack. Implementing it was a tad tricky, and I currently still have a resource leak when shadowing a name not known to anyone (as Go’s garbage collector cannot reasonably see through my interpreter’s implementation to prove unreachability).
[counter] [ 0 ] define [count] [ "counter" eval dup 1 + quote [counter] swap define ] define [] count quote ++ count quote ++ count quote ++ count quote ++ . [ 0 1 2 3 ] counter . 4
When defining a word to itself define a word, the hyperstatic global environment can even be coaxed into emulating mutability. Yet due to the ambiguity introduced in that a name has an attached temporality that its syntax doesn’t proffer, the one-to-one correspondence between program and data is subtly lost. Without the hyperstatic global environment, my interpreter previously didn’t allow for re-declaration, which is unacceptably limiting. The increase in complexity saddens me.
language . "Jonathan Frech's Forth v0.0.0-20260608013404-58b182519535"