6.22.1.6 Closures

Gforth also provides basic closures. A closure is a combination of a quotation (see Quotations) and locals. Gforth’s closures have locals which are filled with values at the closure’s run-time, producing a trampoline xt. When executing that trampoline xt, the closure’s code is executed, with access to the closure’s locals on the locals stack. Modifications of the closure’s locals aren’t persistent, i.e. when the closure EXITs, the modified values are lost.

[{: ( – hmaddr u latest latestnt wid 0  ) gforth-experimental “start-closure”

starts a closure. Closures first declare the locals frame they are going to use, and then the code that is executed with those locals. Closures end like quotations with a ;]. The locals declaration ends depending where the closure’s locals are created. At run-time, the closure is created as trampolin xt, and fills the values of its local frame from the stack. At execution time of the xt, the local frame is copied to the locals stack, and used inside the closure’s code. After return, those values are removed from the locals stack, and not updated in the closure itself.

:}l ( hmaddr u latest latestnt wid 0 a-addr1 u1 ... –  ) gforth-1.0 “close-brace-locals”

end a closure’s locals declaration. The closure will be allocated on the local’s stack.

:}d ( hmaddr u latest latestnt wid 0 a-addr1 u1 ... –  ) gforth-1.0 “colon-close-brace-d”

end a closure’s locals declaration. The closure will be allocated in the dictionary.

:}h ( hmaddr u latest latestnt wid 0 a-addr1 u1 ... –  ) gforth-1.0 “colon-close-brace-h”

end a closure’s locals declaration. The closure will be allocated on the heap.

:}h1 ( hmaddr u latest latestnt wid 0 a-addr1 u1 ... –  ) gforth-1.0 “colon-close-brace-h”

end a closure’s locals declaration. The closure will be allocated on the heap.

:}xt ( hmaddr u latest latestnt wid 0 a-addr1 u1 ... –  ) gforth-1.0 “colon-close-brace-x-t”

end a closure’s locals declaration. The closure will be allocated by the xt on the stack, so the closure’s run-time stack effect is ( xt-alloc -- xt-closure ).

>addr ( xt – addr  ) gforth-experimental “to-addr”

convert the xt of a closure on the heap to the addr with can be passed to free to get rid of the closure

free-closure ( xt –  ) gforth-internal “free-closure”

free a heap-allocated closure

: foo [{: a f: b d: c xt: d :}d a . b f. c d. d ;] ;
5 3.3e #1234. ' cr foo execute

foo creates a closure in the dictionary with a single cell, a floating point, a double, and an xt, and prints the first three values before executing the xt on invocation.

This allows to implement Donald Knuth’s “Man or boy test” proposed in 1964 to test Algol compilers.

: A {: w^ k x1 x2 x3 xt: x4 xt: x5 | w^ B :} recursive
    k  0<= IF  x4 x5 f+  ELSE
        B k x1 x2 x3 action-of x4 [{: B k x1 x2 x3 x4 :}L
            -1 k +!
            k  B  x1 x2 x3 x4 A ;] dup B !
        execute  THEN ;
: man-or-boy? ( n -- ) [: 1e ;] [: -1e ;] 2dup swap [: 0e ;] A f. ;

Sometimes, closures need a permanent storage to be modified; it is even possible that more than one closure shares that permanent storage. In the example above, local variables of the outer procedure are used for this, but in some cases, the closure lives longer than the outer procedure; especially closures allocated in the dictionary or on the heap are designed to outlive their parent procedure.

For those, we have home locations, which are allocated like closures, but their code is directly executed at creation and should provide us with the addresses of the home locations.

: bar ( a b c -- aaddr baddr caddr hl-addr )
    <{: w^ a w^ b w^ c :}h a b c ;> ;

This example creates a home location with three cells on the heap, and returns the addresses of the three locations and the address of the home location. This address can be used to free the home location when it is no longer needed.

<{: ( – hmaddr u latest latestnt wid 0  ) gforth-experimental “start-homelocation”

starts a home location

;> ( ) gforth-experimental “end-homelocation”

end using a home location