I just started learning Forth a month or so ago, and I found this video from Andreas Wagner[1] fun to watch.
If anyone goes through OP's book and find yourself wanting to see Forth in action, I recommend the video.
For anyone who likes playing with small experimental projects, I once made a minimal, esoteric canvas colouring language inspired by Forth and Tixy: https://susam.net/fxyt.html
I mean, that's pretty much every language. The main difference is that the programmer's access to it is unconstrained by things like method call definitions.
Like Forth, Ada has two stacks. Unlike Forth, which uses two stacks to simplify the language, Ada uses two stacks to complexify the language. This generalizes to other language features.
size_t strlcpy (char *dst, const char *src, size_t siz) {
register char *d = dst;
register const char *s = src;
register size_t n = siz;
if (n != 0 && --n != 0) {
do { if ((*d++ = *s++) == 0) break; } while (--n != 0);
}
if (n == 0) {
if (siz != 0) *d = '\0';
while (*s++)
;
}
return(s - src - 1);
}
GCC 12.2.0 compiles this to the following 18 ARM instructions, with -mcpu=cortex-a53 -Os -S: .text
.align 2
.global strlcpy
.syntax unified
.arm
.type strlcpy, %function
strlcpy:
@ args = 0, pretend = 0, frame = 0
@ frame_needed = 0, uses_anonymous_args = 0
@ link register save eliminated.
mov r3, r1
cmp r2, #0
beq .L6
.L14:
subs r2, r2, #1
beq .L3
ldrb ip, [r3], #1 @ zero_extendqisi2
strb ip, [r0], #1
cmp ip, #0
bne .L14
.L4:
sub r0, r3, r1
sub r0, r0, #1
bx lr
.L3:
mov r2, #0
strb r2, [r0]
.L6:
ldrb r2, [r3], #1 @ zero_extendqisi2
cmp r2, #0
bne .L6
b .L4
.size strlcpy, .-strlcpy
If you're not familiar with ARM assembly, I'll tell you that nothing in this entire function uses the stack at all, which is possible because strlcpy doesn't call any other functions (it's a so-called "leaf subroutine", also known as a "leaf function") and because ARM, like most RISCs, puts the subroutine return address in a register (lr) instead of on the stack like amd64, or in the called subroutine like the PDP-8, which doesn't have a stack at all. And the calling convention puts arguments and return values in registers as well. So the function can just move data around between memory and registers and decrement its loop counter and increment its pointers without ever touching the stack.FORTRAN up to FORTRAN 77 didn't support recursion, including indirect recursion, so that you could implement it without a stack.
By contrast, in Forth, instead of registers you use the operand stack. For loop counters you use the return stack. Sometimes you can use the operand stack instead of variables as well, although I think it's usually a better idea to use variables, especially when you're starting to learn Forth—it's much easier for beginners to get into trouble by trying too hard to use the stack instead of variables than to get into trouble by trying too hard to use variables instead of the stack.
The first is a technical problem: the forte of Forth is self-hosted developer tooling in restricted environments: say, under 256KiB of RAM, no SSD, under 1 MIPS, under 10 megabytes of hard disk or maybe just a floppy. In that kind of environment, you can't really afford to duplicate mechanism very much, and programmers have to adapt themselves to it. So you end up using the same mechanism for fairly disparate purposes, with the attendant compromises. But the results were amazing: on an 8080 with 64KiB of RAM and CP/M you could run F83, which gave you virtual memory, multithreading, a somewhat clumsy WYSIWYG screen editor, a compiler for a language with recursion and structured control flow, an assembler, and a CLI and scripting language for your application.
Those environments almost don't exist today. But if you're programming, say, an MSP430 (consider as paradigmatic https://www.digikey.com/en/products/detail/texas-instruments...), you have only 2KiB of RAM, and you could use Mecrisp-Stellaris https://mecrisp.sourceforge.net/
That chip's resources are pretty limited. In a money economy, we measure resources in money; the reason to use a chip with limited resources is to avoid spending money, or to spend less money. That chip costs US$7.40. For US$5.59 you could instead get https://www.digikey.com/en/products/detail/stmicroelectronic...: 100 megahertz, 512MiB of flash, 256KiB of RAM, 50 GPIOs, CAN bus, LINbus, SD/MMC, and so on. And according to Table 33 of https://www.st.com/content/ccc/resource/technical/document/d... it typically uses 1.8μA in standby mode at 25° at 1.7V. That's more than the MSP430's headline 0.1μA from https://www.ti.com/lit/ds/symlink/msp430f248.pdf but it's still low enough for many purposes. (A 220mAh CR2032 coin cell could theoretically supply 1.8μA for 13 years, but only has a shelf life of about 10 years, so the STM32 uses less than the battery's self-discharge current.) That is to say, the niche for such small computers is small and rapidly shrinking.
Also, while the microcontroller might have only 2KiB of RAM, the keyboard and screen you use to program it are almost certainly connected to a computer with a million times more RAM and a CPU that runs a thousand times faster. So you could just program it in C or C++ or Rust and run your slow and bloated compiler on the faster computer, which will generate more efficient code for the microcontroller. The cases where you have to build the code on the target device itself are few and far between.
Forth was designed to make easy things easy and hard things possible. The second problem is a social one: as a result of the first problem, the people who used Forth for that have mostly fled to greener pastures. The Forth community today consists mostly of Forth beginners who are looking for an artificial challenge: instead of making hard things possible, they want to make easy things hard. There are a few oldtimers left who keep using Forth because they've been using it since it did make hard things possible. But even those oldtimers are a different population from Forth's user base in its heyday, most of whom switched to C or VHDL. Most of us have never written a real application in Forth, and we've never had the religious-conversion experience where Forth made it possible to write something we couldn't have written without Forth.
The third problem is also a social one: as a result of the second problem, most Forth tutorials today are written by people who don't really know Forth. I've only briefly skimmed this tutorial, but it seems to be an example of this. For example, I see that it doesn't explain immediate words, much less when to not use immediate words. (If it's ever easier to write something in Forth than in C, it's probably because you can define immediate words, thus extending the language into a DSL for your application in ways that are out of reach of the C preprocessor.) And it doesn't talk about string handling at all, not even the word type, even though string handling is one of the things that Forth beginners stumble over most when they start using Forth (because it doesn't inherently have a heap).
So, I hope the author continues to learn Forth, and I hope they extend their tutorial to cover more aspects of it.
Later I read, that GForth 1.0 should have more string handling words, but then I already had lost hope to find an easy solution. Don't get me wrong, learning the little bit of Forth that I did learn, it was quite interesting, and I would have liked to progress more. I think I also lost hope, because I couldn't see how this stack system would ever be able to handle multi-core and persistent data structures. Things that I have come to use in other niche languages. Also that some projects/libraries are one-man shows/bus factor 1, and the maintainers have stopped developing them. They are basically stale and made by people, which significantly more understanding than any beginner will have for a long time.
I guess to really learn it, one has to read one of the often recommended books and have a lot of patience, until one gets to any parts, where one learns simple things like reading a file line by line.
As for string handling, in my limited experience, string handling in Forth is a lot like string handling in C; you have to allocate buffers and copy characters between them. memcpy is called move, and memset is called fill. You can use the pad if you want, but you can just as well create inbuf 128 allot and use inbuf. There are two big differences:
1. Forth doesn't have NUL-terminated strings like C does, because it's just as easy to return a pointer and a length from a subroutine as it would be to return just a pointer. This is generally a big win, preventing a lot of subtle and dangerous bugs. (Forth is generally more error-prone than C, but this is an exception.)
2. Forth unfortunately does have something called a "counted string", where the string length is stored in the byte before the string data. You can create them with C" (https://forth-standard.org/standard/core/Cq), and Forth beginners often wonder whether to use counted strings. The answer is no: you should never use counted strings, and they should not have been included in the standard. Use normal strings, created with S" (https://forth-standard.org/standard/core/Sq), unless you are calling word or find. https://forth-standard.org/standard/rationale#rat:cstring goes into some of the history of this.
If you want to allocate strings on the heap, which is often the simplest way to handle strings, malloc is called allocate, realloc is called resize, and free is called free: https://forth-standard.org/standard/memory
With respect to multicore and persistent data structures (I assume you mean FP-persistent, as in, an old pointer to a data structure is a pointer to the old version of the data structure), stacks aren't really related to them. Each Forth thread has its own operand stack and its own return stack (and sometimes its own dictionary), so they don't really create interactions between different cores.
https://github.com/tehologist/forthkit
It is an implementation of eforth, a portable forth:
7thaccount•2h ago
https://news.ycombinator.com/item?id=10634918
I'm always interested in hearing people's reactions to Forth though and every now and then you get a cool new story on these threads, so I'm not complaining.
s-macke•2h ago
johnisgood•2h ago
https://factorcode.org/
7thaccount•1h ago
jinwoo68•1h ago
johnisgood•1h ago
Additionally, check basis/ and extra/ on https://github.com/factor/factor. You will find A LOT of goodies there. They even have a framework for Discord bots using latest Discord API version, if you are into that. In any case, you really ought to check, there are too many things to list here.
As someone else pointed out, it is 0.100 which was released not that long ago, and if you compile now, it is 0.101 anyways, but regardless of their versioning, it has been actively maintained ever since, they just did not bump the version for a long time.
kibwen•1h ago
kragen•1h ago
By contrast, assembly language underpins most software people run today: perhaps it's written in Python, which is interpreted by CPython (using a bytecode which is considerably more Forthlike than Wasm, incidentally), which is written in C, which is usually compiled by GCC or LLVM to textual assembly language in order to generate the executable.
kragen•1h ago
vdupras is here in the comments today. He's written a self-hosting interactive operating system in Forth, including a FAT16 filesystem and everything, and a compiler for a useful subset of C: https://duskos.org/
bell-cot•1h ago
s-macke•1h ago
Actually I did a few projects with Forth and I find it very cool:
[0] https://github.com/s-macke/Forthly
[1] https://github.com/s-macke/starflight-reverse
[2] https://s-macke.github.io/concepts-of-programming-languages/...
hobs•53m ago
bxparks•1h ago
I've been reading about Forth for 30-40 years. The dual stack is easy to understand. My problem is that I cannot see how control flow works in Forth, e.g. a simple if-then-else.
I think that something as fundamental as an if-then-else should be obvious in a useful language. Heck, it's obvious in assembly language. But not in Forth.
alexisread•1h ago
In most languages branching is a fundamental construct, it's created here (with comments)
https://github.com/cesarblum/sectorforth/blob/master/example...
Effectively IF compiles a 0= ie. If false and then a dummy target address. THEN (aka ENDIF) compiles the real target address over the dummy one, which is the address after THEN.
kamaal•44m ago
That brings us to the question, when it was invented and people did use it. What kind of problems were they solving with it?
andsoitis•44m ago
Hopefully this is helpful: https://www.forth.com/starting-forth/4-conditional-if-then-s...
bxparks•22m ago
My mental model of Forth is that there is a simple parser that consumes space-delimited keywords. The interpreter looks up that keyword in a dictionary, which gives the address of the machine code that handles that word. The interpreter either makes a subroutine call to that address (subroutine threaded), or jumps to that address (called "direct threaded" if I recall, where the handler jumps back to the interpreter instead of executing a RET).
But that's where my mental model of Forth breaks down, because IF-THEN-ELSE cannot be implemented in that model. So there must be something else fundamental in the Forth interpreter that I don't understand.
addaon•11m ago
The missing bit is IMMEDIATE mode. Words can be tagged as IMMEDIATE, which means that they get executed at compile time (or parse time, for an interpreter), rather than a call to them getting compiled (executed at run time, for an interpreter). IF/ELSE/THEN are then "just" IMMEDIATE mode words -- but you can add your own. The "special sauce" is that IF compiles (easier to talk about for a compiler; generalize as needed) a conditional branch to an unknown address, and puts the address of that branch instruction (or an equivalent) on the /compile time/ data stack; THEN then looks at the address on the /compile time/ data stack and patches that instruction to branch to the correct address. Plenty of subtlety possible, but the basic primitive of IMMEDIATE mode is the key.
kragen•37m ago
From a certain point of view, Forth is just what you get if you take assembly language and look for the simplest way to add properly nesting expressions, properly nesting control structures, subroutines with arguments and return values, arbitrary compile-time computation, an interactive debugging environment, a scripting language, disk storage, and multithreading.
bxparks•17m ago
kragen•11m ago
soapdog•1h ago
idatum•46m ago
Micropython can be similar this way, but it's more constrictive.
nmz•44m ago
Just because you don't specifically use forth does not mean forth is dead.
kragen•24m ago
fallat•41m ago
- Accounting system used for real life taxation in my self-employment: https://gist.github.com/lf94/fcdf41776e14fcc289bac652ea8cb4f...
- Software shader rasterizer for image generation: https://gist.github.com/lf94/f74c927e59b4010d9de001fa2ba8791...
- PS4 controller macro system via an RP2040 & ZeptoForth: https://youtu.be/exayMSQfyqk, https://gist.github.com/lf94/2d64917728594516dee6caf7667d2e4...
- Iambic paddle tap interpreter for morse code practice (once again running RP2040 & ZeptoForth): https://gist.github.com/lf94/95516fa39c3339b685e0fde10f17c97...
- Fixed-point number library to run computations on pretty much any CPU in existence: https://gist.github.com/lf94/ca622ebac14d48915ea976f665f832c...
- 1-bit music synthesis experiments to learn how to make music with a beeper (useful in products, such as Tile): https://www.youtube.com/watch?v=IjTihhFG03o, https://www.youtube.com/watch?v=_6f8PURcPEE
And that's all in my rare spare time.
Forth really shines in microcontroller or esoteric computation machines, but further more, people don't realize their C compilers are billions of dollars of development, and they'd never be able to do it in the first place. A Forth on the other hand can be developed in a month (I'm being honest-to-god realistic here. A lot of people would say "a weekend", but let's be real, anything useful will be more than a weekend. I'm trying to convince you this isn't bullshit :)).
If you have any more questions let me know. I was bit by Forth about 2 years ago but had read about it long ago when I was like 16 and passed it off as too hard. It's the same shit as when FP took off: it's a different mental model, so it will take time to morph your mind.
Edit: read the larger comment below, and they are totally correct:
> most Forth tutorials today are written by people who don't really know Forth
One day I might write a small Forth novella teaching how to actually think about Forth programs. In these 2 years I've had to just practice and write programs to see common patterns or idioms - kind of exactly like when I was learning Haskell years ago.
kragen•17m ago
I don't think it takes billions of dollars in development to write a C compiler, but it does seem like everyone's first C compiler does take at least a year, so US$100k is a good ballpark. I agree that Forth is about an order of magnitude easier. You probably shouldn't consider https://github.com/kragen/stoneknifeforth to actually be a Forth (it's not interactive and doesn't have immediate words) but it did compile itself into a working ELF executable and it did take me almost exactly a month to write (October 02008, specifically). I could probably write a real Forth now in a month.
I would be very excited to read your small Forth novella. Or your small Haskell novella.
pjmlp•40m ago
PaulHoule•26m ago
There weren't a lot of languages which would fit in a tiny space, but FORTH was one of them. Like LISP it's a language where you can (1) implement the language without any kind of recursive parser and (2) write control structures in the language itself because each "word" in forth has both a run-time and compile-time interpretation.
kragen•24m ago