Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

It’s the delimiter between the code that will run when the word is run and the code that gets compiled to be run later.

Typical usage is for the “code that will run immediately” is to store some data, and for the “code that gets compiled to be run later” to use that data.

perhaps the simplest example is CONSTANT, which can be defined like this:

     : CONSTANT ( w "name" -- )
         CREATE ,
     DOES> ( -- w )
         @ ;
Here, the “code that will run immediately” is

  CREATE ,
which a) reads a name from the command line and creates a word with that name, and then takes the top of the stack and stores that directly after the word’s definition.

The “code that gets compiled to be run later” is

  @
which fetches the formerly stored value (taking the address of the formerly created word from the stack)

DOES> has to do some shenanigans to make that work, but that’s an implementation detail, and will be dependent on the particular FORTH being used.



OK, so if we have

  : NAME alpha beta ... psi omega ;
what happens is that at at compile time, a dictionary entry NAME is created, and then the alpha ... omega words are compiled to be run later.

When DOES> is introduced:

  : NAME alpha beta ... DOES> ... psi omega ;
all of the above still holds. We still have a dictionary entry NAME, which denotes all of the words up to the semicolon, including DOES>.

Then, when we execute NAME in a compilation context, because the word sequence contains DOES>, everything to the left of DOES> is specially treated: it is executed immediately in the compilation context and is removed. But that's not all; DOES> doesn't just execute everything to the left and disappear; it leaves something behind: some word which is then combined with the material to the right of DOES> to form the run-time sequence.

In your example, when we run CONSTANT, the part to the left of DOES> fetches a name from the input stream, and creates a word, and then makes the value on the stack the definition.

the accumulation of to-be-run later words is interrupted, and everything before DOES> is done now, at definition time, and removed from the definition.

The CREATE material, when executed, leaves behind a reference to the word denoting the constant. Then DOES> creates a definition for that word, using the remaining material.

Is that more or less it?


> Then DOES> creates a definition for that word, using the remaining material.

Typically (likely always, as sharing code is the main reason DOES was invented), it compiles that code once and magically makes “that word” ‘jump’ to that code. That way, when, for example, you use the definition with DOES> multiple times, you only compile “the remaining material” once.

> Then, when we execute NAME in a compilation context, because the word sequence contains DOES>, everything to the left of DOES> is specially treated

That would be too magical. If you want a word to be executed when compiling code, you make it IMMEDIATE. For the CONSTANT example I gave, that’s not done, as it is executed in interpretation context, and then creates a word, compiles the number from the top of the stack, and then hooks up the word just created to the code compiled earlier.


That's what I was wondering; the multiple times. If we have an ordinary definition : foo a b c d ; of course the "a b c d" is compiled once and tied to foo.

But the code after DOES> is repeatedly referenced in new definitions that are the result of executing the word which contains DOES>, like the CONSTANT example.

The original : CONSTANT CREATE , DOES> @ ; could be entirely compiled so that it contains a compiled sequence for the @ part. When DOES> is executed, it patches a pointer to that part into the word produced by CREATE, and then somehow skips the execution of that part.

How do you manage the storage? There has to be some refcounting or garbage collection. What if four words point to the same instruction sequence, and we FORGET three of them.

Ah, but FORGET works in a LIFO discipline; you can't just forget arbitrary entries. If B was defined using parts of A, then B is newer. You cannot forget A without forgetting B first. I think.




Consider applying for YC's Fall 2025 batch! Applications are open till Aug 4

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: