Defining words — words that make words¶
You will: define a defining word — a word whose job is to make other words. You will build
CONSTANTyourself out of three small meta-words (CREATE,,,DOES>), watch it stamp named values at compile time, grow a little family of folding defining words, and meet the one place mforth says no — the cell-free boundary.You will need: Chapter 3, where
: … ;taught the language a new word. This chapter teaches the language a new word that, in turn, teaches it more words.
In Chapter 3 you learned to define words with : … ;.
Every word you have written since — double, cube, bump, restock
(soon) — has been a colon definition. That is most of Forth.
But Forth has a second, quieter trick, and it is the one that makes Forthers grin: you can define a word whose job is to define more words. A word that, when you run it, stamps out a brand-new word. Forth calls these defining words, and the textbook first example is one you have been using in other languages without a second thought: a constant.
CONSTANT is not built in — you make it¶
In most languages a constant is a keyword the compiler hands you. In
Forth it is not. mforth ships no CONSTANT word at all. Instead it gives
you the three pieces to build one, and CONSTANT is a word you define
in your own source:
That prints 42. Read it in two beats:
- Define-time.
: CONSTANT CREATE , DOES> @ ;is an ordinary colon definition — it just happens to contain the three meta-words. It does nothing yet; it teaches the language a new defining word calledCONSTANT. - Stamp-time.
42 CONSTANT answerrunsCONSTANTwith42on the stack. That call stamps a new word,answer, into the dictionary. From here on,answeris a word like any other —answer .pushes42and prints it.
You have written a word that wrote a word. answer did not exist until
CONSTANT made it.
What the three pieces do¶
CREATE, ,, and DOES> are not words you call on their own — they
only ever appear inside a defining word's body, where together they
describe how to stamp a child. Each has one job:
| Word | When | What it does |
|---|---|---|
CREATE |
inside the body | begins making the new child word, and gives it a private field to hold data |
, (comma) |
after CREATE |
takes one value off the stack and tucks it into that field |
DOES> |
inside the body | everything after it is the child's behaviour, run whenever the child is later called |
So read : CONSTANT CREATE , DOES> @ ; as a sentence: to define a
CONSTANT, create a new word, comma the value into its field, and say
that does> — when you call the child — it fetches (@) that field.
When you run 42 CONSTANT answer:
42is on the stack.CREATEstarts the new wordanswerand gives it a field.,moves42from the stack intoanswer's field.DOES> @is recorded as whatanswerwill do: fetch its field.
Later, answer runs @ against a field holding 42, so it leaves 42
on the stack. The pronunciation of , is "comma" and of DOES> is
"does"; @ is the same "fetch" you met with variables in
Chapter 7.
The compile-time beat: the child is free¶
Here is the part that connects back to the Forth habit you have been building. In Chapter 9 the punchline was factoring is free at runtime — mforth inlines your words, so clarity costs nothing. Defining words have the same shape of payoff, pushed one phase earlier:
CONSTANT runs at compile time. The child it stamps folds to a bare
literal — there is no runtime cost at all.
When you write 42 CONSTANT answer and later use answer, mforth does
not keep a box in memory and read it on every tick. It works the whole
thing out while compiling: the field holds 42, the child's body is
@, so the fetch folds away to just the value 42. By the time your
program runs — whether in the simulator or as mlog pasted into the
game — answer is indistinguishable from having typed 42 yourself. No
storage, no fetch, no instruction spent. The defining word did its work
before the program ever started.
This is why mforth can re-admit a meta-word it deliberately left out of the core language: it is eliminated before anything runs. (For the why behind that design, see The meta layer.)
A small family of defining words¶
CONSTANT is the simplest defining word — its DOES> body just fetches.
But DOES> can do real arithmetic on the field, and it still folds away.
That lets one defining word stamp a whole family of related words.
Here is DOUBLED: each child it stamps pushes twice its stored value.
That prints 42. The DOES> body is @ 2 * — fetch the field, push
2, multiply — so the child built from 21 always pushes 42. And
again, no runtime cost: @ 2 * against a field of 21 folds, at compile
time, to the literal 42.
A second example, building a child that squares its field with DUP *
(the squaring trick from Chapter 2):
That prints 36 then 81. One defining word, SQUARED-C, stamped two
independent children — each carrying its own field, each folding to its
own literal. This is the defining-word payoff: you describe the pattern
once, then mint as many named, zero-cost values as you like.
When mforth says no — the cell-free boundary¶
A defining word's DOES> body can use the field and compile-time
values — literals, arithmetic, stack juggling. What it cannot do is
combine the field with a value that only exists at runtime. Try to, and
mforth stops you:
That DOES> @ + wants to fetch the field (100) and add it to whatever
the caller left on the stack (5) — a value mforth does not know until
the program runs. mforth refuses to compile it:
CellBoundaryError: defining word 'OFFSET' (child 'past-hundred')
DOES> body does not reduce to a compile-time constant —
crosses the cell-free boundary
This is the same spirit as the stack checker from Chapter 3: a whole class of trouble is caught at definition time, not discovered at runtime. A child that mixed in a runtime value could not fold to a literal — it would need a real memory box to live in, and mforth v1 deliberately has none. Rather than quietly grow one behind your back, mforth names the offending word and stops, so the boundary stays visible.
The rule of thumb is short: a DOES> body may compute with its field
and constants, but not with the caller's runtime input. Field plus a
literal (@ 100 +) folds and is fine; field plus a stack value (@ +)
does not and is refused. For the full account of which bodies fold and
which are rejected, see
the meta layer
and the
dictionary reference.
Exercises¶
Write each answer in its own .fs file and check it:
A pass prints a line like ✓ forth-104/01-constant — 2/2 cases pass.
mforth check --scaffold <id> writes a starter file with the marker;
mforth check --solution <id> reveals the reference answer.
Exercise 1 — define CONSTANT¶
id: forth-104/01-constant
Define the defining word CONSTANT ( value -- ) exactly as above
(CREATE , DOES> @), then use it to stamp a constant answer holding
42. The driver pushes answer and prints it.
answer . should print 42.
Exercise 2 — a DOUBLED defining word¶
id: forth-104/02-doubled
Define a defining word DOUBLED whose children push twice their
stored value (CREATE , DOES> @ 2 *). Then stamp a child x from 21.
x . should print 42.
Exercise 3 — a folding family¶
id: forth-104/03-family
Define a defining word SQUARED-C whose children push the square of
their stored value (CREATE , DOES> @ DUP *). Stamp two children — six
from 6 and nine from 9 — to prove one defining word mints a whole
family.
\ @exercise forth-104/03-family
: SQUARED-C CREATE , DOES> @ DUP * ;
6 SQUARED-C six
9 SQUARED-C nine
six . should print 36; nine . should print 81.
What you learned¶
- A defining word is a word that makes other words.
CONSTANTis the classic, and in mforth you define it yourself out ofCREATE,,, andDOES>— there is no built-in. CREATEstarts a child with a private field,,fills the field from the stack, andDOES>describes what the child does when later called.- A defining word runs at compile time; the child it stamps folds to a bare literal, with no runtime cost — the meta-layer echo of Chapter 9's "factoring is free."
- A
DOES>body may compute with its field and constants (@ 2 *,@ DUP *), and one defining word can mint a whole family of children. - The cell-free boundary: a body that mixes the field with a runtime
stack value cannot fold, so mforth refuses it with a
CellBoundaryErrorat definition time rather than growing a hidden memory cell.
Next: 11. Macros — the other half of the meta layer. Where a defining word makes a named value, a macro substitutes a phrase of Forth straight into your code, and it too vanishes before the program runs.