Intermediate

Functions in Forth

Learn how to define and use functions in Forth - word definitions, stack effects, parameters, recursion, locals, and higher-order words with Docker-ready examples

In most languages, a “function” has a name, a parenthesized parameter list, and a return statement. Forth has none of these. Instead, Forth functions are called words, and they communicate exclusively through the data stack. A word pops its inputs off the stack, does its work, and pushes its results back. There are no named parameters and no explicit return statement — what’s left on the stack when the word finishes is the return value.

This is the heart of Forth’s concatenative paradigm: programs are built by composing small words, each of which transforms the stack. Because every word reads from and writes to the same shared stack, a definition like : CUBE DUP DUP * * ; reads almost like a pipeline. The trade-off is that you must keep the stack’s shape in your head, which is why every well-written Forth word carries a stack effect comment such as ( n -- n³ ).

In this tutorial you’ll learn how to define words with : and ;, how to pass multiple inputs and return multiple outputs through the stack, how to write recursive words with RECURSE, how to use named local variables when the stack juggling gets awkward, and how to treat words as values with execution tokens — Forth’s take on higher-order functions.

Defining and Calling Words

A word definition starts with : (colon), followed by the word’s name, its body, and a closing ; (semicolon). The stack effect comment ( before -- after ) documents what the word consumes and produces — it’s a comment, but an essential one.

Create a file named functions.fth:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
\ A word that squares the number on top of the stack
: SQUARE ( n -- n^2 )  DUP * ;

\ A word that takes nothing and prints a greeting
: GREET ( -- )  ." Hello from a Forth word!" CR ;

\ Call SQUARE: push 5, square it, then print the result with .
5 SQUARE . CR

\ Call GREET: no stack input needed
GREET
bye

SQUARE uses DUP to duplicate the top value (so 5 becomes 5 5) and then * to multiply them, leaving 25. The word . (dot) prints and removes the top of the stack. GREET takes no input and leaves nothing behind — its only effect is printing.

Multiple Inputs and Multiple Returns

Because results live on the stack, a Forth word can return several values just as easily as one — it simply leaves more than one item behind. Here a word consumes two numbers and returns both their sum and their product.

Create a file named multi_return.fth:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
\ Consume two numbers, return their sum and product
: SUM-PROD ( a b -- sum prod )
    2DUP +      \ stack: a b sum
    -ROT *      \ stack: sum prod
    ;

3 4 SUM-PROD          \ stack now holds: 7 12 (sum below, prod on top)
." Product = " . CR   \ prints and removes 12
." Sum = "     . CR   \ prints and removes 7
bye

This relies on stack-manipulation words: 2DUP copies the top two items (a ba b a b), + adds the top pair into sum, and -ROT rotates the top three items so the original a b move back to the top (a b sumsum a b) where * can multiply them. The two results, sum and prod, are simply left on the stack for the caller to use.

Recursion with RECURSE

A word’s name isn’t visible inside its own definition (it isn’t fully defined until the closing ;), so Forth provides RECURSE to call the word currently being defined. The classic example is factorial.

Create a file named recursion.fth:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
\ Compute n! recursively
: FACTORIAL ( n -- n! )
    DUP 1 > IF
        DUP 1- RECURSE *   \ n * FACTORIAL(n-1)
    ELSE
        DROP 1             \ base case: 0! and 1! both equal 1
    THEN ;

5 FACTORIAL . CR
bye

For 5, the word checks DUP 1 > (is 5 greater than 1?), and since it is, it computes 5 * FACTORIAL(4). The recursion unwinds down to FACTORIAL(1), where 1 > 1 is false, so the ELSE branch runs DROP 1, returning 1. Multiplying back up the chain gives 120.

Local Variables for Readability

Deep stack juggling can become hard to read. Gforth supports named locals using the Forth-2012 {: ... :} notation. The names are bound from the stack in left-to-right order matching the stack effect — the leftmost name takes the deepest value, the rightmost takes the top.

Create a file named locals.fth:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
\ Evaluate the quadratic a*x^2 + b*x + c using named locals
: QUADRATIC ( a b c x -- result )
    {: a b c x :}      \ bind the four arguments to names
    a x * x *          \ a * x * x
    b x * +            \ + b * x
    c +                \ + c
    ;

\ Evaluate 2x^2 + 3x + 1 at x = 4  ->  2*16 + 3*4 + 1 = 45
2 3 1 4 QUADRATIC . CR
bye

Without locals, this word would need several DUP, SWAP, and ROT operations to reuse x three times. The {: a b c x :} line names the inputs once, so the body reads almost like ordinary algebra. Locals are scoped to the word — they exist only while QUADRATIC runs and are invisible elsewhere. For storage that persists across calls, you’d use a global VARIABLE or CONSTANT instead.

Higher-Order Words with Execution Tokens

Forth words are values too. The ' (tick) word returns the execution token (xt) of the word that follows it, and EXECUTE runs the xt currently on top of the stack. Together they let you pass behavior into a word — Forth’s equivalent of a function argument.

Create a file named higher_order.fth:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
\ Apply a word to a value twice: result = f(f(n))
: TWICE ( n xt -- result )
    DUP >R       \ stash a copy of xt on the return stack
    EXECUTE      \ apply the word once
    R> EXECUTE   \ retrieve xt and apply it again
    ;

: INC ( n -- n+1 )  1 + ;

5 ' INC TWICE . CR   \ INC(INC(5)) = 7
bye

' INC pushes the execution token for INC onto the stack. Inside TWICE, we keep a second copy of that token safe on the return stack with >R / R> while the first EXECUTE runs INC on 5 (giving 6), then apply it again to get 7. Passing a different word — say ' SQUARE — would change the behavior without touching TWICE at all.

Running with Docker

1
2
3
4
5
6
7
8
9
# Pull the official Gforth image
docker pull forthy42/gforth:latest

# Run each example
docker run --rm -v $(pwd):/app -w /app forthy42/gforth:latest gforth functions.fth -e bye
docker run --rm -v $(pwd):/app -w /app forthy42/gforth:latest gforth multi_return.fth -e bye
docker run --rm -v $(pwd):/app -w /app forthy42/gforth:latest gforth recursion.fth -e bye
docker run --rm -v $(pwd):/app -w /app forthy42/gforth:latest gforth locals.fth -e bye
docker run --rm -v $(pwd):/app -w /app forthy42/gforth:latest gforth higher_order.fth -e bye

Each file already ends with bye, so the -e bye is a harmless safeguard that guarantees Gforth exits after running.

Expected Output

Running functions.fth:

25 
Hello from a Forth word!

Running multi_return.fth:

Product = 12 
Sum = 7 

Running recursion.fth:

120 

Running locals.fth:

45 

Running higher_order.fth:

7 

Note the trailing space after each number — the . word always prints a number followed by a single space.

Key Concepts

  • Words are functions — Defined with : name ... ;, a word has no parameter list and no return; it communicates only through the shared data stack.
  • Stack effect comments are documentation — Always annotate definitions with ( before -- after ); they’re the contract that keeps stack code readable.
  • Multiple return values are free — A word returns as many results as it leaves on the stack, so ( a b -- sum prod ) is just as natural as a single return.
  • RECURSE calls the current word — Since a word’s name isn’t bound until its closing ;, recursion uses RECURSE rather than the word’s own name.
  • Locals tame the stack — Gforth’s {: a b :} notation binds inputs to names scoped to the word, replacing long chains of DUP/SWAP/ROT when clarity matters.
  • The return stack is for temporary stashing>R and R> move items aside so the data stack stays clean; always pair them within the same word.
  • Words are first-class via execution tokens' fetches a word’s xt and EXECUTE runs it, enabling higher-order words that take behavior as an argument.

Running Today

All examples can be run using Docker:

docker pull forthy42/gforth:latest
Last updated:

Comments

Loading comments...

Leave a Comment

2000 characters remaining