So what is FORTH? FORTH is a family of stack based programming languages.
This means, that instead of having functions that take arguments
and return some value, you have a last-in-first-out stack where you put your
data and procedures just take what they need from it and sometimes put stuff
back.
As far as abstraction-level goes, it's somewhere between C and assembly,
meaning it's very nice to work with.
As I described it in my
Tetris clone
b-log, in which I talk more about how actual project in FORTH might look like:
it feels a lot like working in C, but without all the unnecessary abstractions
: fib ( n -- fib ) 0 1 rot 0 do over + swap loop swap drop ;
If you never seen FORTH code before, you probably wonder what are you looking at right now. Yes, it's Fibonacci sequence, but that is not the important part.
Lets go through a little FORTH quickcourse.
In, say C, you would print addition of two numbers like this:
#include <stdio.h> int main() { printf("%d", 5 + 6); return 0; }
In Forth, you can achieve the same result with the following:
5 6 + .
First, there is no 'main' function.
Second in FORTH, you have words (procedures) that operate on the stack. Well, you have three stacks (integer, float, return-address), but that's not the point. '+' is a word that takes two integers and returns one. In FORTH, this would be signified by stack-effect comment. Stack-effect comments are used in word definitions to inform programmer what will the word do. The '+' stack comment would look like this:
( n n -- n )
The spaces between '(' and ')' are required, as they are words by themselves. Firs you put what will be taken, then what will be returned. If word does not take or return a value, you just leave the side empty. 'n' means signed integer. For more values, refer to Gforth manual.
Back to numbers. So to perform addition, you first need to put '5' and '6' on the stack. There are no types in FORTH. Everything on stack is 64-bit integer called a 'cell' and each word treats it as needed. When we call '+', it will take the two numbers off the stack and return their addition. After that, we call the '.' word, which takes one item off the stack and prints it into the console.
OK, now let's define our own word:
: add-2 ( n -- n+2 ) 2 + ;
First thing I would like to note is that FORTH uses kebab-case and is case insensitive.
Now, ':' word starts word definition. Whatever is the next token will be the new word's name. Word name can be anything and yes,
: 4 5 ;
does, in fact, redefine the symbol '4'. (as an exercise, return it back to normal)
';' ends word definition, after which it can be used just like any other word.
This is getting a bit long. Stack operation words:
\ stack outcome 1 2 3 4 swap \ => 1 2 4 3 1 2 3 4 over \ => 1 2 3 4 3 1 2 3 4 tuck \ => 1 2 4 3 4 1 2 3 4 rot \ => 1 4 3 2 1 2 3 4 -rot \ => 1 3 2 4 1 2 3 4 drop \ => 1 2 3 1 2 3 4 nip \ => 1 2 4 \ and more \ '\' starts a comment
Control structures.
These are fun. Unlike most modern languages, FORTH has custom ending for each one. This again makes sense, as they are all just words. They can also only be used inside word definitions.
\ cond if ... else ... then ------------- begin \ cond while ... repeat ------------- begin \ do-while loop ... \ cond until ------------- begin \ infinite loop ... again ------------- \ value case \ value of ... endof \ value of ... endof endcase ------------- 10 1 do \ for loop ( max, min -- ) ... \ count can be accessed with 'i' loop
There are a lot of variations of 'do' and 'case'. as always, the manual is your friend.
Now you should be able to decipher the Fibonacci example. There is a lot more to FORTH and even more I don't even know about, but this should be enough to continue this b-log.
As you can see, it's very different from what are most people familiar with, but it does not take long to get used to and once you do, it's very nice way to write code.
Well, at least I think so.
When you start FORTH (which you can right here on ^C btw) you will be greeted by a REPL. There will be no prompt, but don't worry, you don't need it. This makes sense, as FORTH was originally indented as a cross between BASIC and assembly. Of course you can just run it in a script form.
For fellow vim users, vim has nice FORTH syntax highlight out-of-the-box, and treesitter highlighting is thrash. There are some non-highlighted words tho, as it is not made specifically for Gforth.
This might make is seem that FORTH is not a compiled language, but you would be mistaken. Each word is compiled into efficient machine code, it's just very fast due to lack of syntax to complicate things. (and most words are just a bunch of word calls)
This opens a lot of possibilities, such as being able to toggle between running application and REPL, which is convenient for trying to find just the right color/speed/whatever.
This also has it's dark sides. You cannot compile to stand-alone executable, but need to have the FORTH run-time on your machine. Good luck explaining to your user that.
FORTH is in a lot of ways like scheme. It has vary small core with the rest of language built on top of it. This also causes it to have, like scheme, a lot of implementations of various quallity and deviation from the standard.
Basically all you need to implement is a stack, memory allocation, dictionary and you're good to go.
FORTH in general feels in some areas like procedural counterpart to scheme. Everything is a word. All data are one cell on the stack. You can redefine everything...
I don't really know much scheme, but they seem similar.
I am not the kind of person to interact with any community, let alone that of some programming language I'm just learning, but there are some important aspects to note. Raising from it's REPL only origins, there is the idea of factoring. Factoring is the art of making your problem a bunch of smaller problems, eg. many smaller words. What would be one big function in C is supposed to be many words, even if most of them are called only once. Good when the only way to fix a bug is to redefine the entire word.
This may seem a bit inconvenient or even contra productive at first, but you end up writing way more readable code as the result. Each piece of code is automatically explained by it's word name and words start being less code and more just descriptions of code in other words. Think of it as code folding, but instead of folding code by blocks, you fold all code and leave only comments.
I still need to get way better at this.
Second big part of FORTH is it's do-it-yourself attitude. When you open FORTH subreddit, you are greeted by the following quote from FORTH's creator, Chuck Moore:
In particular you need to avoid writing code for situations that will never arise in practice. You need to avoid writing code to handle a general problem that you are never going to encounter. I don't need to solve the general problem when I only have to solve these specific cases.
The FORTH philosophy is to reinvent the wheel, but in a very specific form to fit your needs perfectly and nothing more. How does this affect you as a programmer? Well, there are very few libraries. This is also one thing that allows so many FORTH implementations to exist, if there are not libraries for one FORTH, what's stopping you from using different one?
It's not all that bad tho, as, at least in Gforth, binding to C libraries is very easy task.
Also, if you're just doing it for fun, it's nice to try make your own implementations of otherwise already solved problems, even if the result sucks. (at least my always do...)
As for the community itself, as stated, I did not interact with is yet, but it's obviously smaller than mainstream languages and I would expect it to be kinda peaceful...
Maybe it's just me, but when I worked in low-level languages like C or Zig, I always felt like there is still some abstraction between me and the machine. Like yes, I managed the memory allocation instead of having a garbage collector and yes, there were stuff that felt low-level, but there was still the syntax that made stuff seem complex.
When I defined struct in Zig, imported it into another file, and made array of it, it felt like a bunch of syntax compiled by way smarter people than me and who knows what actually happens. FORTH does not have datatypes, arrays or any similar abstractions, you just have a bunch of numbers. Imagine C, but you can only use void*.
When I defined a struct, it was obvious that I just created a word that returns size of final struct and a bunch of words that just add needed offset to pointer on the stack. When I imported, it was obvious that the runtime just started reading different file for a moment and when I wrote:
create Menu-rects rectangle% MENU-LENGTH * allot
Well, I think you get the point.
I knew the theory behind how computers work and stuff, but it always felt just like that: theory for smarter people to worry about. But while working with FORTH, it's your problem now. And you don't need to be that much smarter either.
Now when I look at struct in C, I no longer see bunch of magic for compiler to handle, but size and a bunch of offsets do data that happen to be initialized to some value while compiling.
I think every programmer could use a bit of that, even if they just end up doing front-end in JS.
Yes, I hear you, assembly would be much better for this and would open your eyes even wider and I do plan to get there eventually. For now tho, it just seems like too much extra work compared to FORTH, so I stick with it for now. I think FORTH is close enough, but hey, what do I know...
For me, FORTH emits strong hacker energy.
Be it it's difference from the mainstream languages, the low-levelness, the simplicity of it, the many implementations, the fact that is can function as stand-alone OS running on some MCU... It just feels like something your average 80's hacker would use on whatever flavor of UNIX he got his hands on and then rant about his success on some BBS, if it makes sense..
Even the fact how you can't compile to executable. Not only you have to share the code to share the program, but you always have the code on your machine. If I wanted to hack on some, say C, program, I would have to first get the code, then get the compiler (ok, C wasn't a good example), install all dependencies and then understand the codebase form whatever documentation it's author left for me.
In FORTH, you just edit the source code already sorted into nice, self-explanatory words and run the program like normally. It almost looks like the code wants you to hack on it.
Sure, it's not the most important aspect of a language, but it adds nice bit of fun/aesthetic to an already fantastic language and I'm all here for it.
I like it.
I like the stack paradigm, playing with pointers is always fun and you learn a lot. In some way, I feel more free than in other languages, where I'm told what to do and how to do it by the compiler. I never got the point of languages like Rust, which main selling point seems to be compiler that constantly screams at you that you are not doing things exactly how some guy thinks every codebase should look like and refuses to compile your completely functional code because of that.
I think that compiler has no rights to tell you what not to do and what not to touch. FORTH allows you to touch everything that was possible to make touchable and believes you to know what to do to not blow yourself up. It just does what you tell it to do, as every good language should. (quite refreshing coming from languages that don't allow you to read attribute unless you specifically request for it to be 'public')
I recommend you to give it a try. You might not stick with it, but it's always good to see what's out there. I'm definitively sticking with it.