6.26.2 How do I write outer locals?

A local instance that is written and read must exist at only one location, its home location. The address of this home location is only read and can be duplicated and passed around. A textbook example might look like this in a hypothetical Gforth with lexical-scoping and explicit dictionary allocation:

\ does not work
: counter ( -- xt-inc xt-val )
  0 {: n :}d
  [:d n 1+ to n ;]
  [:d n ;]
;
\ for usage example see below

Instead, you allocate the home location, and pass its address around:

: counter ( -- xt-inc xt-val )
  align here {: np :} 0 , \ home location
  np [{: np :}d 1 np +! ;]
  np [{: np :}d np @ ;]
;
\ usage example
counter \ first instance
dup execute . \ prints 0
over execute
over execute
dup execute . \ prints 2
counter \ second instance
over execute
dup execute . \ prints 1
2swap \ work on first instance again
dup execute . \ prints 2

This introduction of a home location is called assignment conversion in the programming language implementation literature.

You can also use pure-stack closures in this case:

: counter ( -- xt-inc xt-val )
  align here {: np :} 0 , \ home location
  np [n:d 1 swap +! ;]
  np [n:d @ ;]
;
\ same usage

Instead of dictionary allocation you can also allocate on the heap. For local allocation of the home location you can use variable-flavoured locals (see Gforth locals), but of course then the closures must not be used after leaving the definition in which the home location is defined. E.g.

: counter-example ( -- )
  0 {: w^ np :} \ home location
  np [n:d 1 swap +! ;]
  np [n:d @ ;]
  dup execute cr . 
  over execute
  over execute
  dup execute cr .
  2drop
;
counter-example \ prints 0 and 2

There is actually rarely a reason to use home locations at all, because what the textbook examples do with closures and writable locals can be done in Gforth more directly with structs (see Structures) or objects (see Object-oriented Forth), or in the counter example, simply with create:

: counter ( "name" -- )
  create 0 , ;
: counter-inc ( addr -- )
  1 swap +! ;
: counter-val ( addr -- )
  @ ;
\ usage example
counter a
a counter-val . \ prints 0
a counter-inc
a counter-inc
a counter-val . \ prints 2
counter b
b counter-inc
b counter-val . \ prints 1
a counter-val . \ prints 2

Still, for dictionary and heap allocation Gforth has a home-location definition syntax based on the locals-definition syntax. Here’s a heap-allocation version of counter using closures and the locals-like home-location syntax:

: counter ( -- handle xt-inc xt-val )
  0 <{: w^ np :}h
  np [n:h 1 swap +! ;]
  np [n:h @ ;]
  ;> -rot ;
\ usage example
counter \ first instance
dup execute . \ prints 0
over execute
over execute
dup execute . \ prints 2
counter \ second instance
over execute
dup execute . \ prints 1
free-closure free-closure free throw \ back to first instance
dup execute . \ prints 2
free-closure free-clouse free throw

Here <{: starts a locals scope (similar to a closure itself), then you define (variable-flavoured) locals. :}h (or :}d) finishes the locals definition. Now (and up to ;>) you can use the names of the defined locals. Finally, ;> ends the scope and pushes the start address of the allocated home-location block (also when using :}d for dictionary allocation), for freeing the home-location block later.

We have produced no uses of <{: and ;> in the first 6 years that they were present in (development) Gforth. We think that the reason is that one prefers structs or objects for modifiable data. Therefore, we intend to remove these words in the future. If you want to see them preserved, contact us and make a case for them.

<{: ( compilation – colon-sys ; run-time –  ) gforth-obsolete “start-homelocation”

Starts defining a home location block.

;> ( compilation colon-sys – ; run-time – addr  ) gforth-obsolete “end-homelocation”

Ends defining a home location; addr is the start address of the home-location block (used for deallocation).