I adore Forth and feel that it's in the small handful of languages / approaches to language that will permanently change a developer's understanding of the field. Perhaps if GreenArrays had succeeded well enough to make a few more generations of processor the landscape would be very different. Perhaps not.
Given today's architecture, the impedance mismatch between the stack paradigm and the register architecture costs Forth most of its advantage. Still, if there's another language that allows one to write an interactive assembler for a system with 8k of RAM, I've yet to hear of it.
The minimum investment in Forth, which I recommend to all thoughtful developers, is reading Programming a Problem Oriented Language by Chuck Moore, Forth's inventor.
I'd also recommend looking at Thinking Forth if you want to get some more insight into Forth's philosophy, and really program structuring in general. It's also a good read even for people who don't have much practical use for forth in itself, many of the ideas are language independent.
To whomever runs www.forth.org:
The nameserver bywater.songbird.com is non-responsive.
You have two nameservers on roundrobin.
At least put the dead one second and turn off the roundrobin.
What an absolutely fascinating article. I wonder how useful Forth would be for implementing another language atop a VM (e.g. the JVM, CLR or Lua's VM — or WebAssembly).
Most (naive) VMs are defined as Forth-like stack machines as it's hard to reckon ways to map high-level instructions to load/store register semantics, but much easier to think in terms of stack push/pop. In that sense, Forth lives everywhere, it's just not formally acknowledged as such because it's a compilation target, so you only see it when you go debug the VM in terms of its instruction set.
A way to think about Forth is not as a solution, but a strategy: build the smallest abstraction that works, then build a second abstraction that refines things slightly closer to the domain problem, but explicitly compiles to the semantics of the previous one. If you do this several times you can get a very powerful environment quickly. But it takes time to reinvent the world from scratch, and shipping a project still usually leads to taking on dependencies. "Pure" Forth is just a matter of applying the approach directly to the hardware, resulting in a barebones, loose-cannon system that is clearly very powerful, but too dangerous to use out of the box.
Traditional compiler technology, in contrast, exchanges a big set of dependencies(a compiler, a build system, a module system, etc.) for a predefined semantics, type system, error checking, etc. These things are really useful for applications, but such languages can never be quite as direct since they are general-purpose. The Forth approach is still useful always.
While back I wrote a Forth interpreter. (2.4kloc of portable C, passes nearly all the ANS Forth test suite.) I recommend this to anybody interested in programming languages; you don't have to finish it, but once you get the core interpreter working (in mine, the inner loop is two lines of C!) there's this aha moment and you finally get what the fuss is about, and you can feel your brain expanding.
But as a programming language to work in? Well...
- Forth has no (well, very little) runtime error checking. If your stack underflows, and you're lucky, it'll halt and tell you. But get anything else wrong, and it won't notice, and it'll just silently and invisibly poison its internal state and then crash later in a way that's really hard to debug.
- The language is strongly typed. It just doesn't have any type checking, and if you get the types wrong, it'll silently and invisibly poison its internal state and the crash later etc. You have to remember what type of object you push onto the stack, because you need to use the right kind of word to consume the object again. Does this word push a float? You need to know, because you need to use FDROP instead of DROP to consume it.
- The main culprit here is the compiler, which passes internal state around is by pushing objects onto the compiler stack. e.g. the BEGIN word, which starts a loop, pushes a 'dest' object onto the compiler stack. You have to consume this with UNTIL, WHILE or AGAIN. If you use the wrong word, or exit the definition without consuming it, it'll silently and invisibly poison its internal state etc. So you're now mentally juggling three different stacks: the data stack, the return stack, and the compiler stack, and if you make a mistake anywhere you're going to have a bad day.
- There's a lot of trivia to remember, which you need to remember. EXIT is allowed inside a BEGIN..AGAIN loop. But it's not allowed inside a DO..LOOP loop, because DO..LOOP loops are permitted to store a state object on the return stack. I will retrieve the index of the innermost DO..LOOP loop, and J will retrieve the index of the next DO..LOOP loop... but not if you've pushed something onto the return stack yourself inside the outer loop. If you don't get it right, it will silently and invisibly etc.
- Forth's only really intended to work on bare metal, and this shows. e.g. data space is assumed to increase from HERE up to the top of memory, and there's no facility for disjoint data spaces. The way you define data is to take a pointer to HERE and then just write stuff. All that stuff is defined to be contiguous. There's no scope for, e.g., running out of heap and allocating another block from the operating system. Oh, and try not to run out of memory, because it will etc.
- Stack juggling gets old very, very quickly. (Although some Forths, not mine, support local variables.)
Where Forth really excels is as an assembler substitute, where you're will to focus down and concentrate on every byte of source and you're going to put up with a complete absence of error checking because you need the performance. It's certainly great there. Being able to fit a complete interactive environment into a tiny, tiny space is astonishingly useful for debugging. But I'd hate to have to write anything of any great complexity in it.
I have heard of type-checked Forths; here's one, although I notice it takes a lot of liberties with the language (like not really having a return stack): http://www.arestlessmind.org/2009/02/03/intro.html
But I've never used one. I should, one day; I'm willing to believe that it's a much more pleasant experience than raw Forth. In particular, an embedded Forth with strict type checking but no runtime overhead sounds amazing...
(Aside: the Jupiter Ace was a Z80-based home computer from the 1980s, about the same grade as the ZX81. It came with Forth. Javascript emulator! http://jupiler.retrolandia.net/ It's got games! In Forth! Load one, do SHIFT+SPACE to exit, then 'vlist' will dump the dictionary and 'list word' will decode a word.)
> Forth has no (well, very little) runtime error checking.
I don't think the standard prevents you from implementing that. If you want it, do it.
> The language is [WEAKLY] typed. It just doesn't have any type checking
True. However, a linter that does some type checking seems possible, provided some annotation syntax for the hard cases where it's not possible to infer types.
The thing is, despite its long existence one doesn't see many "Lint for Forth" around, so we can guess people managed to solve this problem in a different way.
> So you're now mentally juggling three different stacks: the data stack, the return stack, and the compiler stack, and if you make a mistake anywhere you're going to have a bad day.
The "compiler stack" is never an issue unless you overuse the "metaprogramming" capabilities of Forth like you'd abuse macros in Lisp.
I understand you might have had that impression if you spent more time implementing the interpreter than implementing programs.
> There's a lot of trivia to remember, which you need to remember. EXIT is allowed inside a BEGIN..AGAIN loop. But it's not allowed inside a DO..LOOP loop
Well of course you're supposed to know that the loop index is stored on the return stack. For a Forth programmer, it's as trivial and obvious as taking care of freeing malloc'ed memory is for a C programmer.
> There's no scope for, e.g., running out of heap and allocating another block from the operating system. Oh, and try not to run out of memory, because it will etc.
That's because it's typically not needed for various reasons. However if you really need it you can:
- make all of your addresses relative so that your system can reallocate the dictionary space; in other words, make your VM run Place Independent Code (PIC).
- create a library that allows you to allocate memory from the OS or the heap.
> Stack juggling gets old very, very quickly. (Although some Forths, not mine, support local variables.)
It's like saying FP is a hoax because you use mutability all the time. The problem is not in the language, it's how you use the language.
> While back I wrote a Forth interpreter. I recommend this to anybody interested in programming languages; you don't have to finish it
You should really finish and actually use it because you don't understand Forth yet.
Oh, and don't be afraid to discard standard compliance. It's a bad trade for you: you get all the constrains and will probably never benefit from the advantages.
What do you mean by self-hosting, here? You compile the compiler with itself? Isn't this just cross-compilation?
(Which, I should add, mine doesn't support. My compiler is written in Forth, but it isn't compiler isn't compiled in Forth --- the Forth subset it's written in is precompiled ahead of time. With awk.)
Interestingly the inventor of Forth agrees with you about the loops. [0]
There was
DO LOOP there was
FOR NEXT and there was
BEGIN UNTIL
DO LOOP was from FORTRAN, FOR NEXT was from BASIC, BEGIN UNTIL was from ALGOL.
What one do we pick for Forth? This (DO LOOP) has two loop control parameters and it is just too complicated. This (FOR NEXT) has one loop control parameter and is good with a hardware implementation and is simple enough to have a hardware implementation. And this one (BEGIN) has variable number of parameters.
I've got a new looping construct that I am using in Color Forth and that I find superior to all the others. That is that if I have a WORD I can have in here some kind of a conditional with a reference to WORD. And this is my loop.
WORD ~~~ IF ~~~ WORD ;
THEN ~~~ ;
I loop back to the beginning of the current definition. And that is the only construct that I have at the moment and it seems to be adequate and convenient. It has a couple of side effects. One is that it requires a recursive version of Forth. This word must refer to the current definition and not some previous definition. This eliminates the need for the SMUDGE/UNSMUDGE concept which ANS is talking about giving a new name. But the net result is that it is simpler.
It would of course not be convenient to nest loops but nested loops are a very dicey concept anyway. You may as well have nested definitions. We've talked over the last fifteen years about such things. Should you have conditional execution of a word or should you have something like IF THEN? Here is an example where I think it pays well in clarity, the only loop you have to repeat the current word.
WORD ~~~ IF ~~~ WORD ;
THEN ~~~ ;
You can always do that and it leads to more intense factoring. And that is in my mind one of the keystones of Forth, you factor and you factor and you factor until most of your definitions are one or two lines long.
(Jeff) You might point out that your semicolon after WORD results in tail recursion and converting the call in WORD to a jump and that is how it functions.
(Chuck) So there is no reason to make that a call since you are never going to go anywhere afterwards so you just make that jump. In fact in all my latest Forths semicolon kind of meant either return or jump depending on the context and it's optimized in the compiler to do that. It's a very simple look back optimization that actually saves a very important resource, the return stack.
Seems to me that this replacement of the tail recursion with a jump is basically TCO.
This article is bonkers. How can you directly compare a programming language to multitasking operating systems with desktop enviroments? In what sense is this a sensible comparison? A comparison of embedded code written in C vs Forth would make sense but starting from Plan9 and then swerving off into embedded programming, all the while laying down a thick layer of condescension... Maybe I'm missing something?
The article was written in the year 2000. A lot has changed since then, in pretty much every realm that he discusses. It's not a bonkers article, but it is an artifact of an earlier era.
Well, the view that we can do without "Operating Systems", or rather have the language be the operating system, is not new. It has always been pretty popular in the Lisp, Smalltalk, Forth... communities.
Daniel Ingalls famously wrote in 1981: "An operating system is a collection of things that don't fit into a language. There shouldn't be one." [1] A few years ago a paper argued that Plan 9 was basically trying to achieve that Smalltalk vision. [2]
And if you think about it, that view is extremely fashinable today, with the rise of unikernels [3]. Granted, they are not used for Desktop systems as of now, but who says they won't be in a few years.
"How can you directly compare a programming language to multitasking operating systems with desktop enviroments?"
People who grew up in the 8-bit era might remember the reverse of this mental shift when they first encountered the IBM PC, and were told they needed an operating system for it. Why is the OS an independent idea from the hardware I bought? Why do I need it? Why isn't everything I need built into the computer itself?
"Maybe I'm missing something?"
It flowed for me, I'll try to explain my reaction.
What we think of as an operating system or a language are specialisations of a common idea: they are mechanism for driving a computer.
Mainstream development practice is all about facading existing technology. You write a web gui that comes from a web server that is set up with a DSL. The webserver calls a perl script that shells out and calls perl libraries and makes system calls. And so on. And then we build complex tools to herd all of the complexity: nixos and node.js and IDEs and LLVM and systemd.
We have created human language to refer to these abstractions: operating system, programming language, compiler, package manager, IDE.
Forth is different. It's a technique for solving problems by eliminating cruft (rather than facading it). It doesn't have a whole lot of respect for facader language ideas.
There are similar projects working in the same spirit, but which have let the bedrock sit further out from the hardware.
There's a crowd who want to bedrock on an array language shell, and they build it in a bizarre dialect of C with a means-to-an-end attitude: that's the operating system project the kparc.com team are working on.
There's a crowd who want to bedrock on an easily-networked file-system+bitmap-display abstraction over the hardware, and who are fond of algol-like programming languages: that's plan 9.
There's a crowd who want to bedrock on linux's excellent suite of hardware drivers: that's suckless.org.
A dev who only lives and breathes IntelliJ might mark a vi-using suckless user as a minimalism extremist, be unable to understand their motive. Whereas a kparc developer will flag the same suckless fan a pragmatist but recognise the shared purpose.
The culture war over systemd is an example of the tension between facaders and minimalists. As the facade-the-world attitude has ramped up, Unix has gradually become less and less effective as a host system. If you accept the facader worldview, systemd is the appropriate response. But systemd alienated the minimalists. Before systemd, linux was effective for people who wanted an expedient shell into their computer with adequate hardware and browser support. Systemd added complexity to their bedrock.
Thanks, this makes the article a bit clearer to me. It's about seeing the operating system as bloat and trying to remove abstractions. Like when I see an editor written in Electron, part of me wonders how many layers of abstraction are present.
Also now I've read a bit more, I can also see how Forth systems can be a bit like operating systems since they normally allow multitasking. So my initial comment was a bit rash (using a different account because I can't remember my password and not at home).
I can only assume that when the article was written in 2000, Plan 9 was getting some press in the venues he was following, so writing an article purporting to be about Plan 9 (but not really about Plan 9 at all) seemed sensible.
Admittedly its not that novel of a structure, but still it looks like fun piece of kit to play with.
Otherwise, in general I think Forth has bit similar culture/community than LISP, even if they come from very different backgrounds. Both feel smugly superior compared to common C programmers and feel slightly bitter from being unfairly pushed into niche. Both highlight the minimalism of their idol and celebrate it by creating bazillion tiny implementations.
> Otherwise, in general I think Forth has bit similar culture/community than LISP, even if they come from very different backgrounds. Both feel smugly superior compared to common C programmers and feel slightly bitter from being unfairly pushed into niche. Both highlight the minimalism of their idol and celebrate it by creating bazillion tiny implementations.
Seconded. Additionally, implementing Lisp in Forth (on a Forth CPU for bonus points) sounds like something one should try for the sheer coolness of it.
What is a system call in Forth? NEXT (load an address and jump to it)? If so, yes, forth "system calls" are faster than Plan 9s, but only because Forth doesn't have the separation of user-/kernel land. It's really not a fair comparison.
Reva Forth (Windows/Mac/Linux)[1] is cool but how about a Forth that does not require an OS?
1. No Plan9/BSD/etc.
The true beauty of Forth to me is the relative ease with which one can boot into a Forth REPL and start controlling hardware, without an "OS".
The idea is a computer with APL as its "OS". The project at kparc.com is closer to this idea than the one at cosy.com. What is evident in both cases that this idea has survived. And that's good.
Because the OS you care about on your smartphone isn't linux or ios, it's webkit/blink, and nobody on earth has the capital and the business case for porting a modern web stack to anything at all different. The BSDs are allowed to play because of POSIX, but only if they hop on the video driver treadmill.
In short, there's no money in it, which is the real takeaway from this article.
Given the quality of the software you want your cellphone to get from a network repository and run, a non-preemptive OS with shared resources would be worthless.
We tried the simple approach. It's a nightmare.
(Yes, on single application embedded CPUs simple is great. Those are disappearing faster than glaciers. Everything has different applications running in parallel nowadays.)
How hard would it be to make a preemptive Forth system? Not that much, I suppose. Or we could have hardware like GreenArray chips [1] with many cores (144) so that every application could have its own core and preemption would not even be necessary.
[1] The author of the article, Jeff Fox, worked for them. He died in 2011.
Add resource protection and you'll get a full blown OS, with a Linux-like performance or worse.
But if you just want it to be preemptive, you'll need a small supervisor, a heavier memory manager, and some context switches. You'll get about the same performance green threads get on modern languages.
I was blown away by this 2011 video of Charles Moore talking about his CPU design and running code, talking about how the different nodes are creating the frequencies to produce the images you see on the projector -
I've noticed in myself how awful it is to be in this industry and see the same problems solved over and over again, with only marginal improvements — and quite often, rather large steps back. It must be even worse for Forth programmers.
I'm on a different side. I notice a lot of great solutions to many problems, but while the ideas are solid, they are rather poorly executed. A lot of things are like that and would really benefit from properly executed solutions. But the industry is not there yet, we don't have nearly enough people with enough knowledge and experience to make things happen at a faster pace.
Even written in 2000, the author was conflating Windows 3.x/95/98, which was flakey as all get out, with Dave Cutler's VMS reboot, the NT kernel, which was/is a pretty excellent OS and pretty fully baked by 1995.
Cheaper SoCs like Arduino and Cortex-M3 run bare metal runtimes, just like on the old days.
But yeah, I guess for single units people will just shell out 10 euro more and use a Linux capable unit.
However there are options to use better languages than C or C++ for bare metal embedded development, but none of them tends to be Forth, rather Basic, Pascal, Ada, Java, .NET, Oberon variants.
Forth does it differently. There is no syntax, no redundancy, no typing. There are no errors that can be detected. Forth uses postfix, there are no parentheses. No indentation. Comments are deferred to the documentation. No hooks, no compatibility. Words are never hyphenated. There's no hierarchy. No files. No operating system.
http://yosefk.com/blog/my-history-with-forth-stack-machines....
I adore Forth and feel that it's in the small handful of languages / approaches to language that will permanently change a developer's understanding of the field. Perhaps if GreenArrays had succeeded well enough to make a few more generations of processor the landscape would be very different. Perhaps not.
Given today's architecture, the impedance mismatch between the stack paradigm and the register architecture costs Forth most of its advantage. Still, if there's another language that allows one to write an interactive assembler for a system with 8k of RAM, I've yet to hear of it.
The minimum investment in Forth, which I recommend to all thoughtful developers, is reading Programming a Problem Oriented Language by Chuck Moore, Forth's inventor.