1x Forth (1999)
20 points by veqq
20 points by veqq
I like the idea of Forth. I don’t like it in practice (even though I just implemented Forth). One reason is it’s puzzle like nature to programming. The other thing—naming things. One of the two hardest problems in the industry (the other being cache invalidation and off-by-one errors). And yet here we are, being asked to create yet more names for things.
Forth tends towards tacit programming, without names of that sort! It takes a while to notice, though.
I haven’t really done much Forth since undergrad (where we made a toy Forth interpreter from scratch as an assignment). One thing I do remember, though, that has a number of parallels is the RPN calculator (HP 49G+) that I used heavily in my EE classes. When I started using it it was completely baffling; why on Earth would someone want this instead of a normal infix calculator?! It didn’t take very long though to start “thinking in stacks” consistently and converting an infix equation on paper into a postfix/RPN calculation happened without really thinking about it.
I have since lost the calculator but pretty consistently use Emacs calc-mode, which is also an RPN calculator.
But for a word (Forth jargon for “function”) it can get a bit crazy. For instance, here’s SEARCH
(Forth’s strstr()
function), in Forth itself:
: SEARCH ( c-addr1 u1 c-addr2 u2 -- c-addr3 u3 flag )
3 PICK 3 PICK 2>R BEGIN
2DUP R@ MIN 2R@ DROP OVER COMPARE 0= DUP IF
2R> 2DROP 2R> 2DROP EXIT
THEN DROP 1 /STRING DUP 0= IF
2DROP 2R> 2DROP 2R> FALSE EXIT
THEN
REPEAT
;
The number of DUP
s and DROP
s (among other stack gyrations) is depressing and confusing to me. Forth purists will probably claim that I am passing too much data on the stack, or that using SEARCH
is wrong because it’s ANS Forth, and ANS Forth is a bloated mess of a language (that Chuck Moore himself ignores). Or that I should stuff the stack paramters into VARIABLE
s to get rid of the stack gyrations. But for the Love Of God, DO NOT USE LOCALS! (because it’s not real Forth).
This example is not helped by the fact that Forth uses a clunky representation for strings.
From having worked in (and built) other catlangs, I think one of the most useful things to do is introduce “utility” stack, and use it instead of (or in addition to) the return stack for this sort of shuffly stuff where you can. But I’m also someone who will just pick up locals if I have the chance.
Tbh, I think the future of stacklangs is multistack languages like mirth. Multiple/on-demand stacks being available addresses a -lot- of Forth’s puzzle-nature, I find, while keeping a lot of the factoring benefits.
I don’t get the revulsion of locals in the Forth community, even if it impacts the definition of TO
(for the record, I did implement them—it’s the second longest bit of code, behind SEE
). And while my implementation is about as expensive as using a VARIABLE
, I can see a way of making them cheaper with the tradeoff of using more memory (a definite tradeoff on an 8-bit CPU). But I’m not sure if there’s a lot of Forth code out there that uses them to see if the tradeoff is worth it.
Is there an example of the multistack use in mirth? I’m curious as to how it works.
I don’t get the revulsion of locals in the Forth community
It’s just cargo-culting Chuck’s words from this very article. Forth is quite culty as far as programming language communities go.
For folks I’ve heard explain it beyond just “locals bad”, it’s that locals can make factoring code out more difficult, because once you’re using locals, you have to re-arrange them in order to pull behavior out.
Where a multistack approach comes in handy is that they seem to split the difference, keeping factoring easy to access, while relieving the amount of deep stack shuffling that’s needed.
My understanding is that every parameter in Mirth can be a stack. https://codeberg.org/CapitalEx/sand-mirth/src/branch/main/src/main.mth#L41-L47 this is a small example of x/y being used in a multistack sort of way, especially as you look at the surrounding functions.
That’s Forth in a form it doesn’t need to be - it’s the kind of Forth I wouldn’t even read, I see PICK
and go to look for documentation of what it’s supposed to do instead. Using variables is fine (but never necessary…) even Chuck does it in some version of colorForth/arrayForth, and his latest Forth-like he talked about at a EuroForth a couple years ago uses registers liberally. In the following substring example I copy the @+ word from the F18A to compare memory and look for substrings. Just breaking up the functionality into smaller words like Chuck would or, better example because his code is more penetrable, Samuel Falvo III, makes clear what you’re actually trying to do (obscured with words like COMPARE
and /STRING
(who comes up with these?)):
variable s
: !s s ! ;
: @s s @ c@ ;
: @s+ s @ c@ 1 s +! ;
: false; 0= if drop rdrop unloop false then ;
: mem= ( aan-f) rot !s ( an-f) 0 do dup I + c@ @s+ = false; loop drop true ;
: prepare ( abc-abcabc) dup 2over rot ;
: setup ( abc-abc) >r >r 1+ r> r> ;
: contains? ( anan-f) rot 0 do prepare mem= if 2drop drop I unloop exit then setup loop -1 ;
My Forth is a little anachronistic because I don’t write ANS Forth or gforth (I do everything in retroforth) but you get the point. You could strip the variables from this if you wanted to, but it doesn’t matter, it’s one address. Forth doesn’t have to be write-only or ugly. It’s just about the approach. I wrote about this a little in the Zen of Forth because I wanted to get this idea out there, that Forth doesn’t need to be complex - just do the thing you want to do.
Part of that is my own implementation quirks (only using ANS Forth words, avoiding as much as possible non-standard words [1]). I’m also not a fan of using VARIABLE
as over the past decade or so, I’ve shifted away from using global variables (which are what VARIABLE
s are, to some degree) as much as possible. I’m also seeing quite a bit of stack noise in your definitions above, and due to the one variable, the word(s) aren’t thread safe (as if Forth supports threads, but it’s the thought).
The issues I have with ColorForth are the issue of accessibility, and that Chuck cheats—there’s a hidden word that switches color, so all he’s done is hide the colons and semicolons. That, and he drastically simplifies things such that he’s willing to use the tool, but others might not (related—one major complaint of my Forth implementation is that is uses a custom 6809 assembler, and it requires some major surgery to use another 6809 assembler (at the very least—it requires the removal of nearly 4,000 lines of tests that my assembler runs)). Everybody complains about software bloat and excessive features they don’t use, but everybody has a different list of “excessive features.” Also, software these days is way more capable and in some instances, things are possible today that weren’t in the past (like live spell checking as you type).
I don’t know, I think I’m rambling here …
[1] I cheated on this—for instance {:
is implemented using five non-standard words “written” in Forth, but the words themselves are headless.
only using ANS Forth words
cheating … [by] drastically simplifying
You are supposed to simplify with new words (“abbreviations” of common patterns)! That is the entire point of Forth!
I don’t see any stack noise, just basic manipulation - the unloop exit stuff is ugly but ANS demands it. In practice I’d just use recursion and a word like : 0; dup 0= if drop rdrop then ;
but I wanted to try and be normal to demonstrate the point that you don’t need to combine all the functionality of “comparing two addresses” into a single complex string handling word when it’s more readable and useful as a few small words that do what you want done.
The issues I have with ColorForth are the issue of accessibility
Retroforth uses sigils to tell the interpreter the type of a token, conceptually reimplementing the same thing, offloading some of the parsing work onto the user for a simplified flow. Chuck even addresses this claim in a paper on colorForth where he used typography (italics, bold text, underlined text etc.) to demonstrate the effectiveness of the pre-parsed (by the programmer) Forth style. In implementation there is one style which is a fair criticism.
all he’s done is hide the colons and semicolons.
Maybe? I haven’t ever read the colorForth source but it’s always felt more like colorForth demonstrates the colons and semicolons were only ever convention around the state of the interpreter and representative of the limitations it has. Retroforth too.
he drastically simplifies things such that he’s willing to use the tool, but others might not
Agreed, we don’t live with a paradigm where colorForth or it’s consequences are appropriate for use anywhere outside domains that are forced to fit it for fun. That world does not exist. Chuck’s idea of an application is at most a couple pages of code - yours is a spell-checker, his is three pages of colorForth that let you manually query for the general-case best matches candidates in the (Forth) dictionary. You’d combine a billion applications to make a modern OS in the least extensible way.
useful as a few small words that do what you want done.
That’s another problem I have with Forth—those few small useful words aren’t as hidden as I would like. Yes, there are Forths out there were you can hide such words, but depending upon the implementation, memory is wasted. And this circles back to one of my initial complaints—having to always be naming things.
You’d combine a billion applications to make a modern OS in the least extensible way.
I don’t quite follow here (and it appears you are putting words in my mouth, unless you are using “you” in the plural sense). I picked my example of a spell checker mainly from this post. Spell checkers went from a separate program (or pass of a program) to a feature that is happening as one types (it’s active as I type this and a missspelling [sic] is underlined in red).
I don’t like Forth much, but I love programming in PostScript – and gs
is available pretty much everywhere and works well.
Even given the same fundamental principles the choice of primitive words makes a big difference. I like the duality between arrays and procedures/blocks. I like that PostScript is late-bound by default – a block is a sequence of symbols – but that you can use bind
to convert it to address-threaded for more speed in less dynamic more performance-critical parts of the code. And, like in Python, Javascript etc the use of dicts is pervasive.
Do you have any recommendations for dipping one’s feet into PostScript? I recall a NASA repo which was all Common Lisp or PostScript and got interested in its features. I’m curious what insights you get from it!
Assuming you know how to program then just the manual, right?
https://www.adobe.com/jp/print/postscript/pdfs/PLRM.pdf
There are 500 pages of English explanations and code examples before you get to the reference material.
Here’s a stupid little brute force thing to print prime numbers, written more in how most people write Forth than Postscript style, with hard to keep track of stack manipulations. Don’t write code like this :-)
And Chuck Moore in that interview agrees that you should not write code like this – he doesn’t like index
or roll
to grab values deep in the stack. But he doesn’t like local variables or registers either. He doesn’t say what he does like, and I have no idea.
/pp {(------) = pstack (-----) =} def
/primes {
2 {
true
2 1 3 index sqrt {
2 index exch
mod 0 eq {
not exit
} if
} for
{dup =} if
1 add
} loop
} bind def
primes
The pp
is just handy debugging aid, not actually used.
bruce@i9:~/programs$ time gs -q -dNODISPLAY -dBATCH primes.ps | head -10
2
3
5
7
11
13
17
19
23
29
real 0m0.018s
user 0m0.012s
sys 0m0.007s
bruce@i9:~/programs$ time gs -q -dNODISPLAY -dBATCH primes.ps | head -10000 | wc -l
10000
real 0m0.097s
user 0m0.091s
sys 0m0.009s
bruce@i9:~/programs$ time gs -q -dNODISPLAY -dBATCH primes.ps | head -100000 | wc -l
100000
real 0m2.373s
user 0m2.372s
sys 0m0.034s
Along with the reference manual (known as the “red book”) linked by brucehoult, the tutorial and cookbook (aka “blue book”) is useful, especially when one is not used to concatenative programming.
There is also Postscript language program design (the green book), but I haven’t tried this one.
Samuel Falvo influenced how I write Forth a lot, where you very literally name things. An example project where he does this well: link.
This is very interesting. Let’s extract a few key points about color Forth (my comments in parens):
A Forth word should not have more than one or two arguments (traditional OOP is like this … one argument is a pointer to an object containing many fields)
the stack should be no more than 3 or 4 deep. (Is that within a function, or total? Transputer’s stack was only 3 deep, and certain kinds of flow control cleared it)
: word becomes simply “word” in red. (Ok, nothing substantive there)
; doesn’t mean end of definition, but only return
can have more than one return. Can have more than one entry point. (i.e. word in red)
i21 CPU has 18 deep stack “effectively infinite”. (Ok, so the 3-4 stack is within a function)
no PICK no ROLL. “The others are on the stack because you put them there and you are going to use them later after the stack falls back to their position. They are not there because your using them now”. (That’s fine, I don’t mind strict stack discipline if I have a few registers or local variables – but he doesn’t like those either. So where?)
people who draw stack diagrams or pictures of things on the stack […] are doing something wrong. (So WHERE do you put your stuff?)
no ELSE. “IF ~~~ ; THEN”. No loops – just recursive calls. (He mentions self-recursion, but obviously mutual recursion is very useful)
“word ;” is optimised to just jump to word
instead of calling it.
So what to make of this?
He just rediscovered Scheme. And in particular “Lambda: The Ultimate GOTO” from 48 years ago. Go read that if you haven’t.
https://en.wikisource.org/wiki/Lambda:_The_Ultimate_GOTO
Color Forth now seems to be just a notation for conventional assembly language. Colon definitions (red words) are now simply labels. Semicolon is tail-call of the previous word. It’s just like Scheme except with arguments on the stack instead of clearly delineated in the function call and definition.
21 CPU has 18 deep stack “effectively infinite”. (Ok, so the 3-4 stack is within a function)
Forth has two stacks - in the 18 deep case he is talking about the return stack, in the 3-4 case he is talking about the data stack.
Forth has two stacks
I’m aware. I first used Forth in I think 1982 … it was called STOIC and was on VAX/VMS
He said 17 deep return stack: “On i21 the return stack is only 17 deep. People who are used to nesting indefinitely might get into trouble here.”
The 18 was quite explicitly about the data stack: “But as to stack parameters, the stacks should be shallow. On the i21 we have an on-chip stack 18 deep. This size was chosen as a number effectively infinite. The words that manipulate that stack are DUP, DROP and OVER period.”
You don’t do DUP, DROP, and OVER on a return stack.
Got it! It was I who was confused. The transcript isn’t that easy to follow sometimes.
Weird that he’d pick 17 for one stack and 18 for the other.
Hmm .. maybe it’s PC+16 and ALUin1+ALUin2+16 ?
If you are at all interested in concatenative languages, but think Forth gets unreadable fairly quickly, check out Factor https://factorcode.org