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.
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
>> The thing that separates Forth from most other languages is its use of the stack. In Forth, everything revolves around the stack
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.
Forth is very enjoyable, and it's always exciting to see someone new discovering it, but it has three big problems.
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.
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...
Very much this. Even when programming for constrained environments, it's almost never necessary to self-host anymore. It's easy to use host-side tools to crunch code down to something that'll work on whatever the target is.
From a practical standpoint, one of the few modern uses where FORTH shines is as a REPL for new chips/SOCs so you can play around with the hardware and see how things actually work/debug the databook.
Forth has always intrigued me as one of those languages (APL and Mumps also come to mind) that appears to have a superpower, for example expressing somewhat complex systems compactly, while at the same time also being flawed enough so that this superpower only appears to be applicable to a small niche.
Given the somewhat sorry state of (lack of) expressiveness and accompanying bloat in programming in general, it would be really interesting to see if that is inevitable, so if the superpower is in fact also the flaw, or if it's possible to extract the superpower from the flaw.
The way you express Forth's superpower is one I haven't seen so far and seems to point a possible way:
> So you end up using the same mechanism for fairly disparate purposes, with the attendant compromises.
Can you tell more about those mechanisms that are used for disparate purposes?
> 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.
So compile-time metaprogramming is not just available as an add-on, but very much "how things are done"?
I agree about Forth being a fatally flawed language with superpowers, although I think we could easily have ended up in a world where Forth played the role of C, which has its own fatal flaws.
Yes, compile-time metaprogramming is very much "how things are done". This is simplified by not having syntax, but I don't think they're inseparable; you could imagine building up a compiler in the same way from an almost-as-minimal base using something like https://jevko.org/, S-expressions, a Prolog-like extensible infix parser, or a Smalltalk-like non-extensible infix parser with an open set of operators. I think most of these would be improvements. PostScript has an only slightly more elaborate syntax than Forth, but uses Smalltalk-style lightweight lambdas (called "quotations" in several other stack languages) to provide control-flow operators through runtime metaprogramming instead of compile-time metaprogramming.
As for "mechanisms used for disparate purposes", for example, the outer (text) interpreter in typical Forths plays the role of the Unix shell, the C-level systems programming language, the assembler syntax, and the user interface to applications such as, traditionally, the interactive text editor. And in https://news.ycombinator.com/item?id=45340399 drivers99 reports using it to parse an input file. The Forth language is not a very good shell command language, not a very good high-level programming language, and not a very good text editor user interface language, but it's adequate for all of these purposes.
The dictionary, similarly, serves to hold definitions for all those purposes. But it also allocates memory in a region-allocator-like way—a byte at a time, if need be. You can use the same words like , to store data into the dictionary directly, in interpretation state:
create myarray 3 , 4 , x ,
Or in a constructor:
: throuple create , , , ; 3 4 x throuple myarray
In traditional Forths like F83, , is also the mechanism for adding an xt to a colon definition, but in ANS Forth compile, was added as a possible synonym which would also permit writing Forth code that was portable to non-threaded-code implementations. https://forth-standard.org/standard/core/COMPILEComma
The operand stack serves to pass arguments and return return values, as well as to hold temporaries, but you can often use it to store a local variable as well, and space on it is dynamically allocated, so it's possible to use it to pass or return variable-sized arrays by value. At compile time, it's used to keep track of the nesting of control-flow structures.
The return stack serves to store return addresses, but also to store loop counters or maybe another local variable. And return-stack manipulation provides you with a relatively flexible form of runtime metaprogramming for things like stackless coroutines, shallow-bound dynamic scoping, and exception handling. Here's an implementation of dynamic scoping (which cannot be used inside a do loop or when you have other stuff on the return stack):
0 value old 0 value where : co 2r> >r >r ;
: let! dup to where where @ to old ! co old where ! ;
Example usage:
decimal : dec. 10 base let! . ;
This temporarily sets base to 10 before calling ., but then restores base to whatever value it had before upon return. A better implementation that uses the return stack instead of old and where to save and restore the values is
Thanks a lot for that very comprehensive and insightful answer! Still digesting it... :-)
I did quite a bit of work in Postscript, it's a fun and interesting language.
A guess:
1. compile-time metaprogramming is where the magic is. That is the superpower.
2. as you mentioned, compile-time metaprogramming is quite a bit easier when you have a language with little to no syntax. Hence Lisp and Forth.
3. Not having syntax, or maybe more precisely: having a programming model that does not require syntax, is the fatal flaw.
4. Where we are today, the two actually are coupled. And this leads to the common schisms: one side sees the fatal flaw, the other side the superpower.
5. There is both a causal relationship between the superpower and the fatal flaw and a historical relationship (that's where it was easy, so that's where it was done first)
6. However, the coupling may not actually be a necessary one.
I think more than necessity, requirements, and fatality we have more of a gradient thing. It isn't necessary to have no syntax to do compile-time metaprogramming, but the effort gradient sort of pushes you in that direction. It isn't impossible to write a web browser in Forth either, just hard.
14 comments
[ 3.4 ms ] story [ 39.5 ms ] threadhttps://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.
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.
[1]: https://youtu.be/mvrE2ZGe-rs
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.
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...
From a practical standpoint, one of the few modern uses where FORTH shines is as a REPL for new chips/SOCs so you can play around with the hardware and see how things actually work/debug the databook.
Given the somewhat sorry state of (lack of) expressiveness and accompanying bloat in programming in general, it would be really interesting to see if that is inevitable, so if the superpower is in fact also the flaw, or if it's possible to extract the superpower from the flaw.
The way you express Forth's superpower is one I haven't seen so far and seems to point a possible way:
> So you end up using the same mechanism for fairly disparate purposes, with the attendant compromises.
Can you tell more about those mechanisms that are used for disparate purposes?
> 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.
So compile-time metaprogramming is not just available as an add-on, but very much "how things are done"?
https://www.forth.com/starting-forth/11-forth-compiler-defin...
And having a bit of compile-time metaprogramming also be the compiler is enabled by effectively not having syntax?
Yes, compile-time metaprogramming is very much "how things are done". This is simplified by not having syntax, but I don't think they're inseparable; you could imagine building up a compiler in the same way from an almost-as-minimal base using something like https://jevko.org/, S-expressions, a Prolog-like extensible infix parser, or a Smalltalk-like non-extensible infix parser with an open set of operators. I think most of these would be improvements. PostScript has an only slightly more elaborate syntax than Forth, but uses Smalltalk-style lightweight lambdas (called "quotations" in several other stack languages) to provide control-flow operators through runtime metaprogramming instead of compile-time metaprogramming.
As for "mechanisms used for disparate purposes", for example, the outer (text) interpreter in typical Forths plays the role of the Unix shell, the C-level systems programming language, the assembler syntax, and the user interface to applications such as, traditionally, the interactive text editor. And in https://news.ycombinator.com/item?id=45340399 drivers99 reports using it to parse an input file. The Forth language is not a very good shell command language, not a very good high-level programming language, and not a very good text editor user interface language, but it's adequate for all of these purposes.
The dictionary, similarly, serves to hold definitions for all those purposes. But it also allocates memory in a region-allocator-like way—a byte at a time, if need be. You can use the same words like , to store data into the dictionary directly, in interpretation state:
Or in a constructor: In traditional Forths like F83, , is also the mechanism for adding an xt to a colon definition, but in ANS Forth compile, was added as a possible synonym which would also permit writing Forth code that was portable to non-threaded-code implementations. https://forth-standard.org/standard/core/COMPILECommaThe operand stack serves to pass arguments and return return values, as well as to hold temporaries, but you can often use it to store a local variable as well, and space on it is dynamically allocated, so it's possible to use it to pass or return variable-sized arrays by value. At compile time, it's used to keep track of the nesting of control-flow structures.
The return stack serves to store return addresses, but also to store loop counters or maybe another local variable. And return-stack manipulation provides you with a relatively flexible form of runtime metaprogramming for things like stackless coroutines, shallow-bound dynamic scoping, and exception handling. Here's an implementation of dynamic scoping (which cannot be used inside a do loop or when you have other stuff on the return stack):
Example usage: This temporarily sets base to 10 before calling ., but then restores base to whatever value it had before upon return. A better implementation that uses the return stack instead of old and where to save and restore the values isI did quite a bit of work in Postscript, it's a fun and interesting language.
A guess:
1. compile-time metaprogramming is where the magic is. That is the superpower.
2. as you mentioned, compile-time metaprogramming is quite a bit easier when you have a language with little to no syntax. Hence Lisp and Forth.
3. Not having syntax, or maybe more precisely: having a programming model that does not require syntax, is the fatal flaw.
4. Where we are today, the two actually are coupled. And this leads to the common schisms: one side sees the fatal flaw, the other side the superpower.
5. There is both a causal relationship between the superpower and the fatal flaw and a historical relationship (that's where it was easy, so that's where it was done first)
6. However, the coupling may not actually be a necessary one.
Which paper?
I think more than necessity, requirements, and fatality we have more of a gradient thing. It isn't necessary to have no syntax to do compile-time metaprogramming, but the effort gradient sort of pushes you in that direction. It isn't impossible to write a web browser in Forth either, just hard.
It also makes a nice starting point for building your own interpreters / designing your own languages.
It’s well worth reading through, even if you don’t know any assembly.
[1] https://github.com/nornagon/jonesforth/blob/master/jonesfort...
https://thinking-forth.sourceforge.net/