Victoria Lacroix

What is Forth?

2026-09-08

Today, I wanted to talk about the weirdest practical programming language that exists. This language is used most commonly in the realms of firmware for physical widgets as well as in the field of ærospace. First however—I need to talk about normal programming languages. I'll use in my example the single most-used family of languages in existence today.

Let's talk briefly about computer spreadsheets.

Most programmers have used Excel, Google Sheets, perhaps LibreOffice for my free software–loving friends. I can say this pretty confidently because most business programming occurs entirely within the confines of spreadsheets. Spreadsheets are comfortable for many. They offer an infinite 2D plane for entering data, and plenty of mathematical formulæ which ingest hard figures and return secondary results. These secondary results can themselves be ingested into other formulæ as many times as necessary to obtain insight.

Any kind of calculation which can be imagined may be done in a computer spreadsheet; These tools are all Turing-complete. This should also shut down any debate on whether spreadsheets constitute a programming language or indeed—because each implementation has varying availability of different formulæ—a family of programming languages.

There are also two important factors in spreadsheet programming which I wanted to highlight as well. The first is that formulæ do not nominally modify data outside of their cell. In fact, they do not modify data at all! They simply watch their input cells and ranges for changes, feed changed data into the formulæ, then show the result as the visibly-inspectable contents of their respective cell and push these changes to other cells which depend on them. When a formula looks into the value of another cell, it only sees what is shown to the user; either a hardcoded value or the result of another formula. In a sense, a formula and a hard value are interchangeable—what distinguishes each is what causes the displayed value to change.

The second thing I wanted to point out is indeed this relationship between cells and the formulæ on which they depend. Spreadsheet engines need special code in them to keep track of which cells contain formulæ dependent on either the values stored in certain other cells or the results of other formulæ. This dependent relationship is not something that computers can handle normally. Teams of software engineers needed to write code to make this happen; The result is opaque to users who may not even be aware that their spreadsheet needs to do a lot of heavy lifting in the background to present to them the specific computation model which is required by spreadsheets.

Spreadsheets thus can and should be viewed as abstract machines—a program written on a real machine that behaves like a very different kind of machine. This, too, is not terribly unusual; Nearly every programming language that exists pretty explicitly presents an abstract machine to its user. It is very much the norm for languages to do heavy lifting in the background to present its user with an opaque reality that hides the real underlying machine. Nearly every programming language has functions (which are similar to formulæ) that take in any number of parameters to output a result or achieve some kind of side effect such as writing data into some place in memory.

There are few exceptions to this rule. Besides the obvious, there is one language which consistently (though not universally) bucks the trend of convenient dishonesty. In many ways, it is the exact opposite of the spreadsheet.

Without further ado…

Forth, The Honest Language

Forth is a family of programming languages which are alien to any programmer who knows them.

For instance, to add two numbers in Forth one would write the following.

1 2 +

In Forth, a backslash followed by a space indicates a line comment. I'll sometimes use this to compare Forth against conventional languages.

The first impression one gets when reading Forth is that the language uses postfix notation (or, "Reverse Polish Notation") to write mathematical expressions, but this is actually ignorant of what really happens.

Forth, like many programming languages, uses a fundamental machine feature called the "stack" to handle temporary values or values which will be used for work. The stack is simply a way to arrange a sequence of data where each new datum is piled on top of the last—the last item in is the first item out. This can create situations where items at the bottom of the stack are kept for occasional use while other work happens above it.

This use of a stack is not unlike how real tasks are done on a computer. In conventional programming languages, a stack is used to track where execution is ocurring in a program. Whenever a jump needs to be made to elsewhere (to run a subroutine, usually), programs automatically push a return address to a stack. Once a subroutine is finished, a return address is popped from the stack, then program execution resumes at that location. Like how spreadsheet software implements functionality to make its formula dependencies opaque to users, all programming languages make return addresses opaque.

Likewise, most programming languages also use a stack to handle parameters passed into a function. This, too, allows values to be passed repeatedly down a call chain and allows a predictable place in memory for these values to be stored. Programming language compilers or interpreters do this stack juggling automatically, in order to opaquely present a more mathematical reality to their users.

So, with all that out of the way, let's reexamine the same example.

1 2 +

This code does three things, each separated by spaces. First, the value 1 is pushed onto the stack. Second, the value 2 is pushed onto the stack. Third, the top two stack values are popped from the stack, added together, and the result—3—is pushed onto the stack.

What looks to be a quirk in Forth's mathematical notation is anything but. Rather, this is a very different way to present logic, but it makes sense. When performing arithmetic, Forth actually asks the user to have her numbers ready before any calculations may be run. Take two numbers, and add them. In conventional mathematics, one takes a number, and adds another. But, remember that most programming languages use a stack to handle parameters to functions. Certain especially well-optimized languages might avoid a stack altogether when performing arithmetic, but they all need to look ahead for a numeric value or a variable that resolves to a number when performing their addition. At no point can a programming language make it possible to begin adding numbers before a second one is read—the ability to notate an addition as "1 + 2" is a convenience feature to opaquely present to the user a reality where grade-school mathematics is directly applicable.

Consider this formula,

3 * 4 + 2 * 5

What is the result? Well, standard arithmetic rules dictate that multiplication must be done before addition, so that's the first step.

12 + 10

Next, the numbers are added to result in 22. Recalling that Forth is "postfixed" and one must push numbers to the stack before operating on them, one would write the initial expression as,

3 4 * 2 5 * +

Take three and four, and multiply them to push 12. Then, take 2 and 5, and multiply them to push 10. Finally, add the two remaining numbers to result in 22.

Take this expression in again. Think of the order of operations. Do the rules of operator precedence take effect here? How can they? Each operator is executed in the order that it is found, and operates on the two topmost stack numbers. There can be no ambiguity here. Note also that at one point, three numbers are on the stack—just before the second multiplication is performed, the stack is [12, 2, 5] because the 12 from the first multiplication is unused until the addition. It is nestled safely below other numbers that are being worked on, waiting for the moment where it is needed.

Many Forthers refer to Forth as a stack-based language, but this is an inherently misleading way to describe it. All programming languages use a stack in ways nearly identical to how Forth uses one. The difference is that most languages hide this behind a veneer of algebra. Forth does not.

In Forth, any series of non-space characters is considered a "word". Numbers are words. Arithmetic operators are words. There are words to manipulate the stack's contents, rearrange them. There are words to copy data into memory. There are words to read data from memory. There are words to create new words. There are words to stop creating new words.

In a conventional programming language, these tasks are handled by all manner of syntactic constructs. Arithmetic is handled by operators (which are written like in conventional mathematics, and which automatically have operator precedence rules), definition of custom functions is handled by special constructs, execution of functions is usually handled by explicitly denoting parameters to pass in.

Take for instance this Lua code:

function greet(name)
	print("Hello, " .. name .. "!")
end

When called by writing greet("World"), this function outputs "Hello, World!" to the console. It's your ordinary hello world program. Before even being able to think about how to define a Forth word to accomplish the same, let's consider how this would work in Forth.

Assuming our Forth has words ".." and "print" which work similarly to this example (dot-dot concatenates strings, and print outputs a string to the console), how might one output the same?

The simple answer when working interactively might be to write,

s" Hello, " s" World" s" !" .. .. print

Notice the space after s"—this is conventional Forth fare. Forth works be separating works by space, so to tell Forth to construct a string one must always use a simple predefined word first. The word s" is executed immediately. It simply scans input for a closing " character, then pushes a string containing everything from the first non-space character after s" to the last character before " onto the stack.

This pushes "Hello, " followed by "World" followed by "!", concatenates the top two items to result in ["Hello, ", "World!"], concatenates again, and finally prints. But, when executing an imagined "greet" word one would only want to push a name to the stack, and let the word handle the rest. Rewriting this so one starts with "World",

\ Start with the parameter.
s" World"
\ Push the first string needed.
s" Hello, "
\ Stack is now ["World", "Hello, "]
\ Swap the top two items.
swap
\ Stack is now ["Hello, ", "World"]
\ They can now be concatenated.
..
\ Now, the rest can be done.
s" !" .. print

Factoring out everything but the "World" parameter, this operation can be summarized as follows.

s" Hello, " swap .. s" !" .. print

In Forth, new words are defined with : and definitions are ended with ;—putting the above into a word, the result is this:

: greet s" Hello, " swap .. s" !" .. print ;

There are many neat things about this way of extending the language. First, it's compact—the entire definition fits comfortably on a single line of text. Second, there's a refreshing lack of syntax. Everything in this definition is simply a word. Words do what they do, operating on the stack. There's no need to collect parameters, or manually specify the bounds of the parameter for the print statement. Print simply operates on the topmost stack item—there's no need to add parenthesis to indicate what it operates on, like in Lua.

Okay, so Forth is a language where things kinda happen tacitly, right? I still disagree. Actually, I would say that Forth is a very explicit programming language. When a word is given, it is always executed immediately. The only assumption is that the programmer knows what a word will do, which is an entirely fair assumption. Words often have side effects, either on the stack or elsewhere in memory where the programmer is storing their variables, but the same can be said of any other language. You've always got a stack of values to pass around—the difference is in whether the language lies to you about it.

One of the consequences of Forth's parameter stack being so explicit is needing to occasionally manipulate stack contents. When dealing with many parameters, it can sometimes become difficult to manage parameters. This is especially true for words which require a large number of items on the stack at once. This is often levied as a point of criticism against Forth by those who are quite accustomed to having many local variables in other programming languages, but the existence of the criticism shows that programmers often fail to really consider how Forth can change the way one structures code.

Let's consider for instance a more complex example. I have two objects on the stack, and I have a word called "bind-property" which takes 5 parameters—including the two objects. This word also consumes all parameters, and requires the two objects to be the first and third parameters (fifth from the top and third from the top, respectively). What I want is to use this word without consuming the two objects.

Thankfully, Forth is fully interactive and permits me to write code as I go. I can interactively solve the problem before doing anything else. Here's what such a session might look like.

( The target order for bind-property is
  [obj1, param1, obj2, param2, param3].
  The stack is currently [obj1, obj2]. )

\ Duplicate the top two items.
2dup \ Stack: [obj1, obj2, obj1, obj2]

\ Push a number 1 to the stack.
1 \ Stack: [obj1, obj2, obj1, obj2, param1]
( In reality, this parameter would be
  something else. I've simplified here. )

\ Swap the top two items.
swap \ [obj1, obj2, obj1, param1, obj2]

\ Push the other params.
2 3 \ […, obj1, param1, obj2, param2, param3]

\ Finally, bind the properties.
bind-property \ Stack: [obj1, obj2]

( Without comments, the sequence goes:
2dup 1 swap 2 3 bind-property )

This is a good start, but you'll notice that I can't neatly package this into a single word. Adding the parameters onto the stack occurs interspersed with other stack-juggling words. Doing it this way would mean needing to juggle the parameters everytime I want to call bind-property. Fine if it only needs to be done once, but quickly reaches nightmare levels once it needs to be done more. I want to ideally just simply push the three string parameters onto the stack, then execute a word that puts everything into place for bind-property and executes it (while also ensuring the two initial objects remain on the stack for later use, perfect if I need to bind multiple properties in sequence).

1 2 3 \ [obj1, obj2, param1, param2, param3]

( First let's get duplicates of the objects.
  This is difficult to do with the third
  parameter in the way. Forth has a word
  called ROLL which pops a number from
  the stack, then moves that many items
  upward by one space. The top item ends
  up below everything that was moved. )
5 roll \ [param3, obj1, obj2, param1, param2]
2swap 2dup \ [p3, p1, p2, o1, o2, o1, o2]

( There are many ways to do what needs to be
  done next, despite the number of items on the
  stack. For now, let's ignore param3. )
6 roll 6 roll \ [p3, o1, o2, p1, p2, o1, o2]

( The word -rot takes the third item from the
  top, and moves it to the top of the stack. )
-rot \ [p3, o1, o2, p1, o1, o2, p2]

( obj2 and param2 are in the right order,
  and param1 and obj1 are correctly
  below them but not in the correct
  order. Not a problem. )
2swap swap 2swap \ [p3, o1, o2, o1, p1, o2, p2]

( Then, a -roll on the correct number of
  items to retrieve param3. )
7 -roll \ [o1, o2, o1, p1, o2, p2, p3]

\ Finally, the actual binding can happen.
bind-property \ [obj1, obj2]

( The full sequence here is
	5 roll 2swap 2dup 6 roll 6 roll
	-rot 2swap swap 2swap 7 -roll
	bind-property
This form can be put into a word, too. )
: do-bind
  5 roll 2swap 2dup 6 roll 6 roll
  -rot 2swap swap 2swap 7 -roll
  bind-property ;

( Now, with two objects on the stack
  one can write `1 2 3 do-bind` to
  correctly execute bind-property. )

Success!

But… That word is awfully long. It's hard to see at first glance what exactly it does, which might explain Forth's reputation for resulting in sometimes hard-to-read code.

The solution however is quite simple: double down, dig deeper, and definitively define. The problem can and should be broken up.

Building off that same example from before,

: hide-param 5 roll ;
: keep-objects 2swap 2dup 6 roll 6 roll ;
: fix-order -rot 2swap swap 2swap 7 ;
: unhide-param 7 -roll ;
: prep-bind
  hide-param keep-objects
  fix-order unhide-param ;
: do-bind prep-bind bind-property ;

( Now, the following is possible
  once the two objects are on the stack:
	prop-a prop-b 1 do-bind
	prop-c prop-d 2 do-bind
	prop-e prop-f 3 do-bind
  The result is code that is both easy
  to read, yet also does not need
  redundant parameters to be stated
  and restated each time. )

In a sense, Forth's property of having code that quickly gets difficult to read with increased size can itself become an excellent driver to write ever better code. I can reuse the constituent parts of the new do-bind word, if I want. I could also simply not reuse them—I am under no obligation. What matters is that my new do-bind gives me a way to express something that my code does, with absolutely minimal repetition.

In a conventional language, the resulting calls to do-bind shown in the comment in the above Forth code would be expressed as follows:

bind_property(obj1,prop_a,
	obj2, prop_b, 1)
bind_property(obj1, prop_c,
	obj2, prop_d, 2)
bind_property(obj1, prop_e,
	obj2, prop_f, 3)

Look at the repetition here. In the Forth example, the objects are already on the stack and stay there after do-bind is executed. In context, this means that do-bind will be called after something else puts the objects onto the stack. The context will make this clear. In another programming language, each parameter to bind_property must be stated explicitly, in order. One could write another function that changes the parameter order, but what's the point? On the other hand, only Forth allows so many opportunities to make one's code as clear as possible by breaking problems down into the smallest units.

That is, in my view, the essence of Forth. It is neither a postfix language, nor a stack language. Forth is a method for building computer programs that prioritizes building solutions using bottom-up thinking, in ways that involve breaking problems down into their very tiniest constituent pieces. Rather than write a program as an algebraic expression, Forth programs describe the steps a computer must take to achieve a goal. Thus I have come to view Forth as a mechanical programming language—a language about logical processes instead of mathematics. It is a programmer's programming language, one which can only be achieved by treating the machine as a machine and not as an obstacle towards implementing an abstract machine for evaluating mathematical expressions.

Reply to this post