Jonathan. Frech’s WebBlog

Writ­ing Forth with an Accent (#301)

Jonathan Frech,

Years ago, back when face masks were still fash­ion­able, I remember first actively read­ing about Forth (Moore 2025), a teaching as­sis­tant I worked for had lended me his copy of the interview. Apart from Charles “Chuck” Moore’s lan­guage’s name, the two things that stuck with me after read­ing his interview were that “6 inches” could somehow be under­stood as an elegant ex­pres­sion and that dic­tio­naries’ def­i­ni­tions are circular.

4 [ dup * ]
#stack
	[ 4 [ dup * ] ]
unquote .
	16

It wasn’t until a cou­ple years later when I came across one of my all-time favourite talks (Creager 2023), where I got in­tro­duced 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 com­pu­ta­tion, but their sim­plic­i­ty is foremost on the page; quadratic-runtime in­ter­pret­ers such as Zpr⁠’⁠(⁠h aren’t much fun when ev­ery­thing is sluggish ir­re­spec­tive of what one writes.

127 ternary [["0" "+" "-"] swap index] map concat .
	"++-0+"

Re­verse Łu­ka­sie­wicz notation al­ways has a certain sheen to it, pos­si­bly due to its alien nature: Ex­pres­sions no longer look like sep­a­rate figures plonked into run­ning text, no pa­ren­the­ses exist to dis­am­big­u­ate non-as­so­cia­tive op­er­a­tions. Ev­ery­thing is writ­ten as if it were spoken with­out ever raising or low­er­ing 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 dis­cov­er­ed in the late sixties. You could envisage a postfix dialect of C with­out much hassle, with the exact same com­pil­er ar­chi­tec­ture, run­ning on the exact same van Neumann ma­chines, with the exact same un­com­pilable poly­mor­phic se­man­tics. Such en­deav­ours surely exist (Purdy 2017) (Pestov 2008), yet stray far away from Forth, pos­si­bly on­ly imitating its syn­tac­tic 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 in­ter­est­ed in in­ter­pret­ing the com­pil­er as a mapping from (dyed) english to ma­chine in­struc­tions as close to the identity. (Moore 2013) Such focussed interest in the zeroth meta level is un­der­stand­able given his biography as a hard­ware engineer; stacks don’t fit into registers and higher-order types lead to un­de­cid­ably long com­pi­la­tion 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 re­ject the meta level, how­ev­er, your lan­guage cannot be reflexive. Opposite to S-ex­pres­sions, which an­ec­dot­al­ly first were used to describe ex­e­cut­able M-ex­pres­sions, their fusion being the bed­rock of Lisp’s re­flex­iv­i­ty, Forth de­scen­dants ap­pear to have found their way to ho­mo­ico­nic­i­ty by reifying the ma­chine-inspired syntax into a symbolic form.

[ 1000 [1] [*] primrec len ] dup . unquote .
	[ 1000 [ 1 ] [ * ] primrec len ]
	2568

Forth isn’t symbolic. Forth isn’t struc­tured. It is much more a lan­guage toolkit for fa­cil­i­tating the so-in­clined 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 ma­chine code, which makes it closer to a rep­re­sent­ation than a lan­guage.

[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 con­cat­e­na­tive programs (von Thun), paired with a con­flu­ence statement about the in­ter­pre­ta­tion se­man­tics, β-equiv­a­lence feels more syn­tac­tical.

[range] [ over - swap quote [+ 1 - quote ++] ++ [[]] swap primrec ] define

3 9 range .
	[ 3 4 5 6 7 8 ]

Lisp’s in­flu­ence on how we con­tem­po­rar­i­ly conceive of programs I see ev­ery­where, again and again. How in­flu­en­tial 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

Soft­ware archival tech­niques, how we pre­sent our views on discrete ma­chines to those that come after us, weighs heavy on con­tem­po­rary minds who see so many things once cher­ish­ed 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 se­man­tics⸺com­pare the daunt­ing task to archive the still evolv­ing ISO C++ stan­dard in a way anyone can mean­ing­ful­ly read it in a few cen­tu­ries’ time or, still non-trivial, correctly in­ter­pret­ing the sub­tle­ties in the Go lan­guage spec­i­fi­ca­tion (Griesemer 2025) to ab­stract­ly defining the concept of a word and of a stack fol­low­ed by a fully boot-strapp­able im­ple­men­ta­tion writ­ten in stack-aware words⸺as well as its affinity to fac­tor­i­za­tion⸺at many levels of un­der­stand­ing, parts of the pro­gram can be under­stood in iso­la­tion and ex­changed for a more fit­ting part with­out in­tro­ducing global brit­tle­ness into the pro­gram. Then again, I sense these prop­er­ties might be but a syn­tac­tic illusion of Forth, in­de­pen­dent­ly re­pro­duc­ible in every en­vi­ron­ment.

[x] [ "y" ] define
[y] [  x  ] define
[x] [ "x" ] define

x y ++ .
	"xy"

I haven’t quite warmed up to the hy­per­stat­ic global en­vi­ron­ment (Falvo II. 2008, i), but I get how you need some fa­cil­i­ty to reclaim parts of lex­i­cal scoping in the un­struc­tured, flat world of a singular stack. Im­ple­ment­ing it was a tad tricky, and I cur­rent­ly still have a resource leak when shadowing a name not known to anyone (as Go’s gar­bage col­lec­tor cannot rea­son­ably see through my in­ter­pret­er’s im­ple­men­ta­tion to prove un­reach­abil­i­ty).

[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 hy­per­stat­ic global en­vi­ron­ment can even be co­axed into emulating mu­ta­bil­i­ty. Yet due to the am­bi­gu­i­ty in­tro­duced in that a name has an at­tached tem­po­ral­i­ty that its syntax doesn’t proffer, the one-to-one cor­re­spon­dence be­tween pro­gram and data is subtly lost. With­out the hy­per­stat­ic global en­vi­ron­ment, my in­ter­pret­er pre­vi­ous­ly didn’t al­low for re-dec­la­ra­tion, which is un­ac­cept­ably limiting. The increase in com­plex­i­ty saddens me.

language .
	"Jonathan Frech's Forth v0.0.0-20260608013404-58b182519535"