Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Closures in kForth

382 views
Skip to first unread message

Krishna Myneni

unread,
Oct 16, 2022, 3:54:59 PM10/16/22
to
kForth uses a separate control stack instead of using the data stack,
and it permits nested :NONAME definitions. This makes it easy to define
both quotations and closures simply in Forth source.

For quotations, the definitions of [: and ;] are provided in
ans-words.4th (from the distribution package). These definitions are

: [: postpone [ :noname ; immediate

: ;] postpone ; ] postpone literal ; immediate


To open a closure which takes a single integer parameter we can define
the word corresponding to Gforth's "[N:D" as follows in kForth:

: [n:d
1 cells allocate drop dup
postpone literal postpone !
postpone [: postpone literal postpone @ ; immediate

The closure may be terminated with ";]" just as for a quotation.

Examples of using the single parameter closure:

: test1 3 [n:d dup * ;] execute . ;

: test2 3 2 + [n:d dup * ;] execute . ;

TEST1 prints 9 when executed, and TEST2 prints 25 when executed. It's
important to note the distinction between a quotation and a closure. The
run-time evaluated parameter on the stack prior to the closure is bound
as a fixed value in the closure.

Since kForth performs run-time type checking when address values are
required, a variant of the single parameter closure is needed for a
single address parameter:

: [a:d
1 cells allocate drop dup
postpone literal postpone !
postpone [: postpone literal postpone a@ ; immediate

It is easy to generalize the closure to take more than one parameter
from the stack. Recent examples have shown the utility of closures for
setting the compilation semantics of a word in a dual-semantics system;
however, kForth is still a single-xt + immediate flag system at present.

--
Krishna Myneni





minf...@arcor.de

unread,
Oct 17, 2022, 1:56:24 AM10/17/22
to
Citing Wikipedia:
Operationally, a closure is a record storing a function together with an environment.
The environment is a mapping associating each free variable of the function
(variables that are used locally, but defined in an enclosing scope) with the value
or reference to which the name was bound when the closure was created.

IOW in Forth terms, to make a closure just make a quotation and give it a copy
of the locals stack frame of the word it was built in.

Or am I missing something?

Marcel Hendrix

unread,
Oct 17, 2022, 3:05:02 AM10/17/22
to
On Monday, October 17, 2022 at 7:56:24 AM UTC+2, minf...@arcor.de wrote:
[..]
> IOW in Forth terms, to make a closure just make a quotation and give it a copy
> of the locals stack frame of the word it was built in.

When you exit a called word, its locals go away. You'll need yet another stack.

-marcel

minf...@arcor.de

unread,
Oct 17, 2022, 3:46:01 AM10/17/22
to
Sure, the copy of the environment must be static to be there when the inner
enclosed function is called from outside the outer enclosing function.

I was just musing whether it is sufficient to just have the locals of the outer function
copied (static) into the inner function, or whether a copy of some global upper
data/fp-stack elements would be needed as well.

Anton Ertl

unread,
Oct 17, 2022, 3:47:09 AM10/17/22
to
"minf...@arcor.de" <minf...@arcor.de> writes:
>Citing Wikipedia:
>Operationally, a closure is a record storing a function together with an environment.
>The environment is a mapping associating each free variable of the function
>(variables that are used locally, but defined in an enclosing scope) with the value
>or reference to which the name was bound when the closure was created.
>
>IOW in Forth terms, to make a closure just make a quotation and give it a copy
>of the locals stack frame of the word it was built in.
>
>Or am I missing something?

In other programming languages, a closure is when you pass a function
around that refers to a variable that is not local to it (in
particular, a local variable of one of the containing functions).
E.g., in Forth consider the following code (and don't think about how
it is implemented; these other languages use garbage collection for
this kind of stuff):

\ first example
: n+ {: n :} [: n + ;] ;
3 n+ constant 3+
2 3+ execute . \ 5

\ second example
: foo {: x :} [: to x ;] [: x ;] ;
1 foo constant x> constant >x
2 foo constant y> constant >y
x> execute . \ 1
y> execute . \ 2
3 >x execute
4 >y execute
x> execute . \ 3
y> execute . \ 4

Note that the second example produces four closures, two (X> and >X)
that refer to one instance of X, and two (Y> and >Y) that refer to a
different instance. Because these locals instances are writable, you
cannot just make a copy of them for each closure.

In 2018 I finally set out to design such a feature for Forth. Of
course I wanted to make the implementation simpler. I read up on the
topic and found that the Scheme people had invented flat-closure
conversion and assignment conversion as implementation techniques for
this stuff, and my idea was to make this explicit, and I wrote a paper
about it. I gave it to Bernd Paysan to read, and he found additional
simplifications, and implemented the result. So I had to rewrite much
of the paper, resulting in our EuroForth paper [ertl&paysan18].

In our extension as described in the paper, you pass data on the stack
(rather than through implicitly by referencing an outer local name) to
the closure, and put it into a local inside the closure (this code
works on Gforth):

: n+ ( n -- xt ) [{: n :}d n + ;] ;
3 n+ constant 3+
2 3+ execute .

The memory needed for the closure is allocated explicitly, in this
case in the dictionary (the D in :}D indicates that). Note that this
is allocated when N+ is called, not when it is compiled.

When I worked on the talk for EuroForth 2018, I realized that the
important point about closures for programming is not about locals,
but about transferring data from the closure creation time (when N+ is
called in the example above) to closure execution time. For this
point you don't need locals at all, and I mentioned that in my talk
(see the video [ertl&paysan18] or the "(eliminate locals)" part on
slide 11); at that point this was just an academic point, with no
intention of any practical application. A while later Bernd Paysan
told me that he had actually found this idea practically useful, and
had implemented the idea (with a different syntax from what I
presented on the slide):

: n+ ( n -- xt ) [n:d + ;] ;
3 n+ constant 3+
2 3+ execute .

The word [n:d indicates the start of a closure that takes one data
stack item from the stack at closure creation time (i.e., when N+ is
performed) and puts this item on the data stack at closure execution
time; the closure is stored in the dictionary. The N in [N:D
indicates that a single data-stack item is transferred (there is also
D (two cells) and F (one float)), the D indicated the dictionary
(there is also L (local stack) and H (heap, i.e., ALLOCATE)). If you
need to transfer more stack items, we don't have pure-stack words for
that; you can then use the closure syntax that puts the items in
locals.

@InProceedings{ertl&paysan18,
author = {M. Anton Ertl and Bernd Paysan},
title = {Closures --- the {Forth} way},
crossref = {euroforth18},
pages = {17--30},
url = {http://www.complang.tuwien.ac.at/papers/ertl%26paysan.pdf},
url2 = {http://www.euroforth.org/ef18/papers/ertl.pdf},
slides-url = {http://www.euroforth.org/ef18/papers/ertl-slides.pdf},
video = {https://wiki.forth-ev.de/doku.php/events:ef2018:closures},
OPTnote = {refereed},
abstract = {In Forth 200x, a quotation cannot access a local
defined outside it, and therefore cannot be
parameterized in the definition that produces its
execution token. We present Forth closures; they
lift this restriction with minimal implementation
complexity. They are based on passing parameters on
the stack when producing the execution token. The
programmer has to explicitly manage the memory of
the closure. We show a number of usage examples.
We also present the current implementation, which
takes 109~source lines of code (including some extra
features). The programmer can mechanically convert
lexical scoping (accessing a local defined outside)
into code using our closures, by applying assignment
conversion and flat-closure conversion. The result
can do everything one expects from closures,
including passing Knuth's man-or-boy test and living
beyond the end of their enclosing definitions.}
}

@Proceedings{euroforth18,
title = {34th EuroForth Conference},
booktitle = {34th EuroForth Conference},
year = {2018},
key = {EuroForth'18},
url = {http://www.euroforth.org/ef18/papers/proceedings.pdf}
}

- anton
--
M. Anton Ertl http://www.complang.tuwien.ac.at/anton/home.html
comp.lang.forth FAQs: http://www.complang.tuwien.ac.at/forth/faq/toc.html
New standard: https://forth-standard.org/
EuroForth 2022: https://euro.theforth.net

Anton Ertl

unread,
Oct 17, 2022, 3:53:50 AM10/17/22
to
Krishna Myneni <krishna...@ccreweb.org> writes:
>To open a closure which takes a single integer parameter we can define
>the word corresponding to Gforth's "[N:D" as follows in kForth:
>
>: [n:d
> 1 cells allocate drop dup
> postpone literal postpone !
> postpone [: postpone literal postpone @ ; immediate

This allocates only a single cell for all closures created by a single
source-code location of "[N:D". I expect that this fails the
following test case:

: n+ [n:d + ;] ;
3 n+ constant 3+
5 n+ constant 5+
1 3+ execute . \ should print 4
2 5+ execute . \ should print 7

Ruvim

unread,
Oct 17, 2022, 5:50:32 AM10/17/22
to
On 2022-10-16 19:54, Krishna Myneni wrote:
> kForth uses a separate control stack instead of using the data stack,
> and it permits nested :NONAME definitions. This makes it easy to define
> both quotations and closures simply in Forth source.
>
> For quotations, the definitions of [: and ;] are provided in
> ans-words.4th (from the distribution package). These definitions are
>
> : [: postpone [ :noname ; immediate
>
> : ;] postpone ; ] postpone literal ; immediate
>
>
> To open a closure which takes a single integer parameter we can define
> the word corresponding to Gforth's "[N:D" as follows in kForth:
>
> : [n:d
>     1 cells allocate drop dup
>     postpone literal postpone !
>     postpone [: postpone literal postpone @ ; immediate
>
> The closure may be terminated with ";]" just as for a quotation.

It means that this ";]" generates code that will always return the same
xt in run-time. But a stateless closure should have a unique xt for
every distinct bound value at the least.


Actually, instead of [n:d ... ;] you can use a more simple tool —
partial application.

: partial1 ( x xt1 -- xt2 )
\ create a partially applied definition xt2
\ by fixing the top argument for xt1
state @ >r
2>r :noname r> r> lit, compile, postpone ;
r> if ] then
;
\ NB: in your system this partial1 should properly work
\ during compilation of another definition too.


: foo ( u|n -- xt ) [: dup * + ;] partial1 ;

2 foo alias f2
3 foo alias f3

0 f2 . \ 4
1 f2 . \ 5
2 f2 . \ 6

0 f3 . \ 9
1 f3 . \ 10
2 f3 . \ 11


As we can see, "[n:d ... ;]" is equivalent to "[: ... ;] partial1"


"partial2" can be conceptually defined via "partial1" as:

: partial2 ( x x xt1 -- xt2 ) partial1 partial1 ;

To create a partially applied definition in the heap, we can have
"partialh1", "partialh2", etc. To fix a floating-point value we can have
"fpartial1".

Also, different variants can be passed as arguments to a general
"partial" word, or as immediate arguments to a recognizer, e.g.
"partial:heap:xxr", "partial:dict:x", "partial:once:d" (using the
corresponding data type symbols).

"partial:once:..." creates a closure that may be executed only once, and
after that its memory is freed automatically.


--
Ruvim

Krishna Myneni

unread,
Oct 17, 2022, 7:36:18 AM10/17/22
to
On 10/17/22 02:47, Anton Ertl wrote:
> Krishna Myneni <krishna...@ccreweb.org> writes:
>> To open a closure which takes a single integer parameter we can define
>> the word corresponding to Gforth's "[N:D" as follows in kForth:
>>
>> : [n:d
>> 1 cells allocate drop dup
>> postpone literal postpone !
>> postpone [: postpone literal postpone @ ; immediate
>
> This allocates only a single cell for all closures created by a single
> source-code location of "[N:D". I expect that this fails the
> following test case:
>
> : n+ [n:d + ;] ;
> 3 n+ constant 3+
> 5 n+ constant 5+
> 1 3+ execute . \ should print 4
> 2 5+ execute . \ should print 7
>

Yes, it does fail the above tests -- only one cell is being allocated.
So, yes, back to the drawing board.

--
Krishna



Krishna Myneni

unread,
Oct 17, 2022, 7:41:09 AM10/17/22
to
On 10/17/22 04:50, Ruvim wrote:
> On 2022-10-16 19:54, Krishna Myneni wrote:
>> kForth uses a separate control stack instead of using the data stack,
>> and it permits nested :NONAME definitions. This makes it easy to
>> define both quotations and closures simply in Forth source.
>>
>> For quotations, the definitions of [: and ;] are provided in
>> ans-words.4th (from the distribution package). These definitions are
>>
>> : [: postpone [ :noname ; immediate
>>
>> : ;] postpone ; ] postpone literal ; immediate
>>
>>
>> To open a closure which takes a single integer parameter we can define
>> the word corresponding to Gforth's "[N:D" as follows in kForth:
>>
>> : [n:d
>>      1 cells allocate drop dup
>>      postpone literal postpone !
>>      postpone [: postpone literal postpone @ ; immediate
>>
>> The closure may be terminated with ";]" just as for a quotation.
>
> It means that this ";]" generates code that will always return the same
> xt in run-time. But a stateless closure should have a unique xt for
> every distinct bound value at the least.
>

Yes.

>
> Actually, instead of [n:d ... ;]  you can use a more simple tool —
> partial application.
>
>   : partial1 ( x xt1 -- xt2 )
>     \ create a partially applied definition xt2
>     \ by fixing the top argument for xt1
>     state @ >r
>     2>r :noname r> r> lit, compile, postpone ;
>     r> if ] then
>   ;
>   \ NB: in your system this partial1 should properly work
>   \ during compilation of another definition too.
>

Will try it, but the first problem needs to be fixed also.

>
>   : foo ( u|n -- xt ) [: dup * + ;] partial1 ;
>
>    2 foo alias f2
>    3 foo alias f3
>
>    0 f2 . \ 4
>    1 f2 . \ 5
>    2 f2 . \ 6
>
>    0 f3 . \ 9
>    1 f3 . \ 10
>    2 f3 . \ 11
>
>

Krishna



Ruvim

unread,
Oct 17, 2022, 12:24:55 PM10/17/22
to
To fix the first problem, the form:

[n:d ... ;]

should be translated into:

[: ... ;] partial1

So, we need to generate some code after the quotation.
We have the following options for that:

- introduce a special ending word (instead of ";]")
- do active parsing (i.e., that "[n:d" does parsing till ";]"
- redefine ";]"

The first option is simplest for illustration.
Let it be "cl[n:d ... ]cl"


: cl[n:d postpone [: ; immediate

: ]cl postpone ;] postpone partial1 ; immediate


That's all.

The test word:

: foo ( u|n -- xt ) cl[n:d dup * + ]cl ;

works as before:

>>
>>    : foo ( u|n -- xt ) [: dup * + ;] partial1 ;
>>
>>     2 foo alias f2
>>     3 foo alias f3
>>
>>     0 f2 . \ 4
>>     1 f2 . \ 5
>>     2 f2 . \ 6
>>
>>     0 f3 . \ 9
>>     1 f3 . \ 10
>>     2 f3 . \ 11
>>


"partial1" can be implemented in other system-specific way to be more
efficient.


--
Ruvim

Krishna Myneni

unread,
Oct 17, 2022, 4:14:52 PM10/17/22
to
I tried your definition of partial1 (substituting POSTPONE LITERAL for
"LIT,") and it works as advertised in kForth. Cast into Anton's example,

: partial1 ( x xt1 -- xt2 )
state @ >r 2>r :noname r> r> postpone literal compile, postpone ;
r> if ] then ;
ok
: n+ [: + ;] partial1 ;
ok
3 n+ constant 3+
ok
5 n+ constant 5+
ok
1 3+ execute .
4 ok
2 5+ execute .
7 ok

It is noted that the word PARTIAL1 is STATE-dependent.

> To fix the first problem, the form:
>
>   [n:d ... ;]
>
> should be translated into:
>
>   [: ... ;] partial1
>
> So, we need to generate some code after the quotation.
> We have the following options for that:
>
>   - introduce a special ending word (instead of ";]")
>   - do active parsing (i.e., that "[n:d" does parsing till ";]"
>   - redefine ";]"
>
> The first option is simplest for illustration.
> Let it be "cl[n:d ... ]cl"
>
>
>   : cl[n:d  postpone [:  ; immediate
>
>   : ]cl  postpone ;]  postpone partial1  ; immediate
>
>
> That's all.
>
> The test word:
>
>   : foo ( u|n -- xt ) cl[n:d dup * + ]cl ;
>
> works as before:
>

Yes, it works in kForth. Slightly rewritten:

: cl[n:d postpone [: ; immediate
ok
: ]cl postpone ;] postpone partial1 ; immediate
ok
: n^2+ ( u|n -- xt ) cl[n:d dup * + ]cl ;
ok
2 n^2+ constant 2^2+
ok
3 n^2+ constant 3^2+
ok
1 2^2+ execute .
5 ok
2 2^2+ execute .
6 ok
1 3^2+ execute .
10 ok
2 3^2+ execute .
11 ok

--
Krishna


Ruvim

unread,
Oct 17, 2022, 9:06:44 PM10/17/22
to
Great!

>
> : partial1 ( x xt1 -- xt2 )
>     state @ >r 2>r :noname r> r> postpone literal compile, postpone ;
>     r> if ] then ;
>  ok
> : n+ [: + ;] partial1 ;
>  ok
> 3 n+ constant 3+
>  ok
> 5 n+ constant 5+
>  ok
> 1 3+ execute .
> 4  ok
> 2 5+ execute .
> 7  ok
>
> It is noted that the word PARTIAL1 is STATE-dependent.


By "STATE-dependency" I usually imply STATE-dependent execution
semantics. But the execution semantics of "partial1" does not depend on
STATE, i.e. it does not behave differently depending on STATE.
So, it neither depends on STATE, nor affects STATE.

In other words, it's impossible to detect any difference in the
"before-after" picture when you start it in interpretation state, and
when you start it in compilation sate.


--
Ruvim

S Jack

unread,
Oct 18, 2022, 9:03:25 AM10/18/22
to
On Monday, October 17, 2022 at 2:47:09 AM UTC-5, Anton Ertl wrote:

> E.g., in Forth consider the following code (and don't think about how
> it is implemented; these other languages use garbage collection for
> this kind of stuff):

In assembly a called routine can allocate stack space for local
variables and can access the stack space of its ancestors should
it need to. And when the routine returns, all its locals go away.
No need for heaps nor garbage collecting. I'm guessing that
"closure" is just some high level code wanting to do the same.
--
me

Krishna Myneni

unread,
Oct 18, 2022, 10:15:33 AM10/18/22
to
I think you misunderstood closures. A closure creates a function
(xt in Forth) which compiles into it the parameter(s) passed to it.
The function has persistence. Furthermore, each runtime invocation
of the closure creates a distinct function (xt) with persistence.

-- Krishna

minf...@arcor.de

unread,
Oct 18, 2022, 10:48:13 AM10/18/22
to

S Jack

unread,
Oct 18, 2022, 12:12:48 PM10/18/22
to
On Tuesday, October 18, 2022 at 9:15:33 AM UTC-5, km361...@gmail.com wrote:

> I think you misunderstood closures. A closure creates a function

That may very well be the case. But going by the definition that
in the link minf just posted:

"A closure is any function which closes over the environment in which
it was defined. This means that it can access variables not in its
parameter list."

It appears to me that the essence of closure is not how the function
arrives at its parent but that once invoked by its parent it can
access its parents environment (locals).
--
me

S Jack

unread,
Oct 18, 2022, 12:29:15 PM10/18/22
to
An "escaping closure" may be a little more involvd.
--
me

Krishna Myneni

unread,
Oct 18, 2022, 4:54:59 PM10/18/22
to
I amend my statement: "I probably don't understand closures."

The link by minf is useful.

--
Krishna

S Jack

unread,
Oct 19, 2022, 12:47:02 AM10/19/22
to
On Tuesday, October 18, 2022 at 11:29:15 AM UTC-5, S Jack wrote:

Crude but closure?
frogd
go
-- anonymous function
anon latest pfa dfa @ ' lit cfa , , ppn + ppn . ; imm
-- forth word with local data (not locals)
create foo 2 ,
main 10 * [ dup enter ] ; \ anonymous function closed over foo
-- another such forth word
create bar 6 ,
main 660 [ dup enter ] ; \ anonymous function closed over bar
drop \ discard anonymous function xt
i. 4 foo ==> 42
i. bar ==> 666
ok
--
me

minf...@arcor.de

unread,
Oct 19, 2022, 3:07:53 AM10/19/22
to
After reading through the link, I really wonder whether Forth (in its classic form)
can have REAL closures at all.
IIUC a complete Forth closure environment would comprise
- the upper stack(s) elements that are used within the inner function
- only those locals of the outer function that are used within the inner function
- those global variables/values that are used within the inner function

Now say a closure necessitates a global VALUE being filled with some certain number
in order to work correctly. It had been okay at closure construction time or during
first run of the outer function.

But now what happens when the closure is called at a time when this global VALUE
is not properly set yet/anymore?

IMO implementing such a beast in Forth would be real overkill. The only slightly
limited concept by Paysan/Ertl makes much more sense.

After all, there are also OO packages with hidden methods and variables...

minf...@arcor.de

unread,
Oct 19, 2022, 4:01:43 AM10/19/22
to
minf...@arcor.de schrieb am Mittwoch, 19. Oktober 2022 um 09:07:53 UTC+2:
> km361...@gmail.com schrieb am Dienstag, 18. Oktober 2022 um 22:54:59 UTC+2:
> > On Tuesday, October 18, 2022 at 11:12:48 AM UTC-5, S Jack wrote:
> > > On Tuesday, October 18, 2022 at 9:15:33 AM UTC-5, km361...@gmail.com wrote:
> > >
> > > > I think you misunderstood closures. A closure creates a function
> > > That may very well be the case. But going by the definition that
> > > in the link minf just posted:
> > >
> > > "A closure is any function which closes over the environment in which
> > > it was defined. This means that it can access variables not in its
> > > parameter list."
> > >

Another test case. For simplicity I use the following syntax
[[: ... ;]] means closure
[: ... ;] means quotation (should be familiar by now)
{: ... :} means locals (standard)

: CLOSTEST
{: a :} 1 to a pi 1000e f* \ f: 3141.59, local a is 1
[: {: b :} b 2* ;] \ d: xtquot f: 3141.59, local a is 1 <- this is the environment
[[: {: c :} 1 swap execute f>s a + c ;]] fdrop ; \ d: xtquot xtclos

CLOSTEST NIP 3 SWAP EXECUTE \ d: 2 3142 3
\ 1 swap execute -> 2 <- executes xtquot from the environment
\ f>s -> 2 3141 (f>s rounds towards zero)
\ a + -> 2 3142
\ c -> 2 3142 3 <- 3 had been passed to execute xtclos

Unfortunately I couldn't test it... ;-)

minf...@arcor.de

unread,
Oct 19, 2022, 4:22:58 AM10/19/22
to
minf...@arcor.de schrieb am Mittwoch, 19. Oktober 2022 um 10:01:43 UTC+2:
> Another test case. For simplicity I use the following syntax
> [[: ... ;]] means closure
> [: ... ;] means quotation (should be familiar by now)
> {: ... :} means locals (standard)
>
> : CLOSTEST
> {: a :} 1 to a pi 1000e f* \ f: 3141.59, local a is 1
> [: {: b :} b 2* ;] \ d: xtquot f: 3141.59, local a is 1 <- this is the environment
> [[: {: c :} 1 swap execute f>s a + c ;]] fdrop ; \ d: xtquot xtclos
>
> CLOSTEST NIP 3 SWAP EXECUTE \ d: 2 3142 3
> \ 1 swap execute -> 2 <- executes xtquot from the environment
> \ f>s -> 2 3141 (f>s rounds towards zero)
> \ a + -> 2 3142
> \ c -> 2 3142 3 <- 3 had been passed to execute xtclos
>
Oops .. should be: 0 CLOSTEST NIP ...

Paul Rubin

unread,
Oct 19, 2022, 5:49:04 AM10/19/22
to
"minf...@arcor.de" <minf...@arcor.de> writes:
> But now what happens when the closure is called at a time when this
> global VALUE is not properly set yet/anymore?

I thought VALUEs had to be set at their time of creation, so the "yet"
part doesn't seem like an issue. Is there a way for them to become
unset? If not, the "anymore" part doesn't seem like an issue either.

minf...@arcor.de

unread,
Oct 19, 2022, 8:09:54 AM10/19/22
to
To make it clearer (with the BASE variable here instead of some VALUE):

: OUTER hex [[: . ;]] ;
DECIMAL 10 OUTER DECIMAL EXECUTE --> A

Yes or no?

S Jack

unread,
Oct 19, 2022, 9:31:38 AM10/19/22
to
On Wednesday, October 19, 2022 at 2:07:53 AM UTC-5, minf...@arcor.de wrote:
> >
> After reading through the link, I really wonder whether Forth (in its classic form)
> can have REAL closures at all.

As closure is a concoction to deal with variables, produced by
languages typically dealing with variables, one would think that the
concept would be of little value in Forth where variables are
anathema. (I say this with some jest.)
--
me

S Jack

unread,
Oct 19, 2022, 11:32:10 AM10/19/22
to
On Wednesday, October 19, 2022 at 8:31:38 AM UTC-5, S Jack wrote:
An escaping closure:

create foo 660 ,
main {: dat @ 6 + . -1 dat +! ;} ;

foo \ create escaping closure
-- foo no longer active but closure still using foo data
i. dup execute ==> 666
i. dup execute ==> 665
i. dup execute ==> 664
drop
ok
--
me

dxforth

unread,
Oct 19, 2022, 11:37:53 AM10/19/22
to
ANS-Forth is not amused.


Paul Rubin

unread,
Oct 19, 2022, 2:22:03 PM10/19/22
to
"minf...@arcor.de" <minf...@arcor.de> writes:
> : OUTER hex [[: . ;]] ;
> DECIMAL 10 OUTER DECIMAL EXECUTE --> A
> Yes or no?

OUTER there runs HEX, then leaves a closure on the stack. Not really
much of a closure since it doesn't capture anything. Then you run
DECIMAL which restores the output base to 10. Then you execute the
closure, which should print "10" rather than "A". The closure is only
supposed to capture locals that are in scope when it is created, not
other stuff like BASE. Here is the Scheme equivalent:

(define base 20) ; start with a distinct value so we know it gets set
(define (decimal) (set! base 10))
(define (hex) (set! base 16))
(define (outer) (hex) (lambda (n) (display `(,base ,n))))

(decimal)
(let ((closure (outer)))
(decimal)
(closure 10))

This prints (10 10).

S Jack

unread,
Oct 19, 2022, 2:28:57 PM10/19/22
to
On Wednesday, October 19, 2022 at 10:32:10 AM UTC-5, S Jack wrote:
> On Wednesday, October 19, 2022 at 8:31:38 AM UTC-5, S Jack wrote:
Escaping closures for two clocks:
-1 const -1
: clock latest pfa dfa dup
ppn literal ppn ? ppn -1
ppn literal ppn +!
; imm

create clockA 0 ,
main dat ! {: clock ;} ;
create clockB 0 ,
main dat ! {: clock ;} ;
666 clockA
i. dup execute ==> 666
i. dup execute ==> 665
i. dup execute ==> 664
42 clockB
i. dup execute ==> 42
i. dup execute ==> 41
i. dup execute ==> 40

i. swap execute ==> 663
i. execute ==> 39
ok

minf...@arcor.de

unread,
Oct 19, 2022, 5:20:58 PM10/19/22
to
Paul Rubin schrieb am Mittwoch, 19. Oktober 2022 um 20:22:03 UTC+2:
> "minf...@arcor.de" <minf...@arcor.de> writes:
> > : OUTER hex [[: . ;]] ;
> > DECIMAL 10 OUTER DECIMAL EXECUTE --> A
> > Yes or no?
>
> The closure is only
> supposed to capture locals that are in scope when it is created, not
> other stuff like BASE.

So this is the definition of "closure environment"?

IOW a closure within an outer function that has no locals, but does some athletic
stack juggling instead, is a quotation?

Krishna Myneni

unread,
Oct 19, 2022, 6:53:42 PM10/19/22
to
On 10/18/22 09:48, minf...@arcor.de wrote:
..
Based on the broader perspective, the version of the closure in my
original post might qualify as a closure when used (one or more times)
only within the containing definition.

--
Krishna


S Jack

unread,
Oct 20, 2022, 11:37:42 AM10/20/22
to
On Wednesday, October 19, 2022 at 1:28:57 PM UTC-5, S Jack wrote:

frogd -p
go
-1 const -1
: countdown: ( n -- xt )
create , "[: dat ? -1 dat +! ;]" eval ;

666 countdown: countdownA
i. dup execute ==> 666
i. dup execute ==> 665
i. dup execute ==> 664

42 countdown: countdownB
i. dup execute ==> 42
i. dup execute ==> 41
i. dup execute ==> 40

i. swap execute ==> 663
i. execute ==> 39
ok
--
me

minf...@arcor.de

unread,
Oct 22, 2022, 8:26:42 AM10/22/22
to
Yes, with Forth we can only get as close as practical to closures, but not in their pure meaning.

IIRC someone already mentioned the funarg problem:
https://www.softwarepreservation.org/projects/LISP/MIT/Weizenbaum-FUNARG_Problem_Explained-1968.pdf


Krishna Myneni

unread,
Oct 23, 2022, 5:52:50 PM10/23/22
to
On 10/22/22 07:26, minf...@arcor.de wrote:
> Krishna Myneni schrieb am Donnerstag, 20. Oktober 2022 um 00:53:42 UTC+2:
>> On 10/18/22 09:48, minf...@arcor.de wrote:
>> ..
>>> From a broader perspective:
>>> https://stackoverflow.com/questions/220658/what-is-the-difference-between-a-closure-and-a-lambda/36878651#36878651
>> Based on the broader perspective, the version of the closure in my
>> original post might qualify as a closure when used (one or more times)
>> only within the containing definition.
>>
>
> Yes, with Forth we can only get as close as practical to closures, but not in their pure meaning.
>

I'm not sure why you say that we cannot implement them in their "pure
meaning." Do you mean because we are not dealing with purely symbolic
expressions?

Forth closures of the type implemented by Anton and Ruvim are found to
be quite useful when we need to generate an anonymous definition at
runtime, which binds runtime-computed values.

Ruvim's original definition of PARTIAL1 is state-dependent, i.e. it
contains an expression of the sort, "STATE @ IF ... THEN." On a
dual-semantics system it is possible to define PARTIAL1 without any
reference to STATE as follows (in Gforth):

: partial1 ( x xt1 -- xt2 )
2>r :noname r> r> ( -- xt1 x )
postpone literal
compile,
postpone ;
;
compsem:
['] partial1 compile,
]
;

Example of use (from Ruvim):

: n^2+ [: dup * + ;] partial1 ; ok
5 n^2+ constant 5^2+ ok
6 5^2+ execute . 31 ok

The definition of PARTIAL1 may also be used to define a closure of the
form suggested by Ruvim.

I thought the above dual-semantics definition of PARTIAL1 would port
easily to Vfx Forth simply by replacing COMPSEM: with NDCS: --
unfortunately, it does not.

NDCS: apparently takes upon itself the liberty to modify the behavior of
"COMPILE," -- i.e. it breaks "COMPILE," just as SET-OPTIMIZER in Gforth
breaks "COMPILE,".

The following definition of PARTIAL1 does work in Vfx Forth:

: partial1 ( x xt1 -- xt2 )
2>r :noname r> r>
postpone literal
compile,
postpone ;
;
ndcs:
['] partial1 postpone literal postpone execute ] ;

--
Krishna




Paul Rubin

unread,
Oct 23, 2022, 8:21:28 PM10/23/22
to
Krishna Myneni <krishna...@ccreweb.org> writes:
> I'm not sure why you say that we cannot implement them in their "pure
> meaning." Do you mean because we are not dealing with purely symbolic
> expressions?

The methods you gave seem to allocate storage for the saved locals in
the dictionary, with no way to reclaim the storage once you are done
with the closure. That's not so great if you program in a style that
creates a lot of closures on the fly. I don't know if there are other
issues besides that. Languages that promote using lots of closures
usually reclaim the storage by garbage collection. Idk if there are
other good ways to do it. Or if you're only making a few closures, it
is probably ok to just retain their storage permanently.

minf...@arcor.de

unread,
Oct 24, 2022, 3:34:00 AM10/24/22
to
Krishna Myneni schrieb am Sonntag, 23. Oktober 2022 um 23:52:50 UTC+2:
> On 10/22/22 07:26, minf...@arcor.de wrote:
> > Krishna Myneni schrieb am Donnerstag, 20. Oktober 2022 um 00:53:42 UTC+2:
> >> On 10/18/22 09:48, minf...@arcor.de wrote:
> >> ..
> >>> From a broader perspective:
> >>> https://stackoverflow.com/questions/220658/what-is-the-difference-between-a-closure-and-a-lambda/36878651#36878651
> >> Based on the broader perspective, the version of the closure in my
> >> original post might qualify as a closure when used (one or more times)
> >> only within the containing definition.
> >>
> >
> > Yes, with Forth we can only get as close as practical to closures, but not in their pure meaning.
> >
> I'm not sure why you say that we cannot implement them in their "pure
> meaning." Do you mean because we are not dealing with purely symbolic
> expressions?

Not at all, and neither am I disputing the usefulness of closures in Forth.

But just consider the fact that in Forth function (word) parameters are passed thru
the stack(s) and no/mixed/all stack parameters can be moved to locals.
Global parameters are not passed at all.

"Pure" closures within an enclosing function have access to their enviroment
i.e. to ALL parameters of the enclosing function, not just to its locals.
In this meaning a Forth closure concept that just accesses/copies locals is rather limited.

It would not be absolutely impossible to mimick this in a Forth system, but ...

Whether "symbols or symbolic expressions" are imported into the closure
is an implementation finesse of the compiler. Just disallow POSTPONE for
closures.. ;-)

minf...@arcor.de

unread,
Oct 24, 2022, 4:50:47 AM10/24/22
to
Krishna Myneni schrieb am Sonntag, 23. Oktober 2022 um 23:52:50 UTC+2:
> On 10/22/22 07:26, minf...@arcor.de wrote:
> > Krishna Myneni schrieb am Donnerstag, 20. Oktober 2022 um 00:53:42 UTC+2:
> >> On 10/18/22 09:48, minf...@arcor.de wrote:
> >> ..
> >>> From a broader perspective:
> >>> https://stackoverflow.com/questions/220658/what-is-the-difference-between-a-closure-and-a-lambda/36878651#36878651
> >> Based on the broader perspective, the version of the closure in my
> >> original post might qualify as a closure when used (one or more times)
> >> only within the containing definition.
> >>
> >
> > Yes, with Forth we can only get as close as practical to closures, but not in their pure meaning.
> >

> Forth closures of the type implemented by Anton and Ruvim are found to
> be quite useful when we need to generate an anonymous definition at
> runtime, which binds runtime-computed values.

Unfortunately right now I have no access to other Forth systems to check by myself:
Even when a Forth closure concept only treats locals, the following test might
be helpful:

: OUTER
{: a :}
[[: a . {: b :} b TO a ;]]
a . ;
2 OUTER CONSTANT XTCLOS -> prints 2
3 XTCLOS EXECUTE -> prints 2
4 XTCLOS EXECUTE -> prints 2 or 3 ?
5 OUTER CONSTANT XTCLOS2 -> prints 5
6 XTCLOS EXECUTE -> prints 2 or 3 or 5 ?
7 XTCLOS2 EXECUTE -> prints 2 or 5 or 6 ?

But IIRC Anton's implementation already covers writing to free variables (upper locals).

none albert

unread,
Oct 24, 2022, 9:14:18 AM10/24/22
to
In article <87zgdmd...@nightsong.com>,
I find it hard to imagine a situation -- programming problem --
where you need a lot of closures. And indeed a few closures is
easy enough.
I'm prepared to be convinced to think about adding any feature
that makes life easier. So convince me.

Groetjes Albert
--
"in our communism country Viet Nam, people are forced to be
alive and in the western country like US, people are free to
die from Covid 19 lol" duc ha
albert@spe&ar&c.xs4all.nl &=n http://home.hccnet.nl/a.w.m.van.der.horst

Paul Rubin

unread,
Oct 24, 2022, 2:28:45 PM10/24/22
to
albert@cherry.(none) (albert) writes:
> I find it hard to imagine a situation -- programming problem --
> where you need a lot of closures. And indeed a few closures is
> easy enough.

Rather than concoct an example specifically made for that purpose, here
is a practical problem given as part of a productivity experiment, where
the most compact solution (Haskell) used closures to represent geometric
regions, making it easy to represent unions and intersections of regions
by making more closures:

https://web.cecs.pdx.edu/~apt/cs457_2005/hudak-jones.pdf

See page 5 for the problem description.

> I'm prepared to be convinced to think about adding any feature
> that makes life easier. So convince me.

I think that style doesn't make much sense in Forth, especially without
garbage collection. Does the style make life easier? I think so. Is
it necessary or important? I would say you could do the same thing in
other ways, though with less convenience.

S Jack

unread,
Oct 24, 2022, 10:27:03 PM10/24/22
to

Countdown


-1 const -1

: cntr:
create ( -- xt )
0 ,
"[: dat @ 0> if -1 dat +! fi dat ;]" eval
does ! ;

defer countdownA
cntr: cntrA is countdownA ( countdownA closed over cntrA )
666 cntrA
i. countdownA ? ==> 665
i. countdownA ? ==> 664
42 cntrA
i. countdownA ? ==> 41
i. countdownA ? ==> 40
ok
--
me

S Jack

unread,
Oct 24, 2022, 10:43:32 PM10/24/22
to
Countdown


-1 const -1

-echo
: countdown ( n a -- xt )
{: ?DUP if -1 SWAP +! fi ;} over cell+ !
! ;

: .count ( a -- )
set
$0 . &0 $x1 ;

: count_all
begin $0 while &0 .count
repeat ;

local data on data stack
: foo ( n -- )
0 0 sp@ set
$2 &0 countdown
count_all 3drop ;
i. 3 foo ==> 3 2 1

local data in dictionary
: bar ( n -- )
here 2 cells allot set
&0 countdown
count_all -2 cells allot ;
i. 4 bar ==> 4 3 2 1

mixing countdowns
: zoo
0 0 0 0 sp@ set
$4 &0 countdown
$5 &2 countdown
&0 .count
&2 dup dup .count .count .count
&0 .count
2drop 4drop ;

i. 30 3 zoo ==> 3 30 29 28 2

Counter in ring buffer
mpad dup val mp_counter1
2 cells +
dup val mp_counter2
2 cells +
mpad:set
3 mp_counter1 countdown
30 mp_counter2 countdown
i. mp_counter1 .count mp_counter2 .count ==> 3 30
i. mp_counter1 .count mp_counter2 .count ==> 2 29
i. mp_counter1 .count mp_counter2 .count ==> 1 28

-fin-
ok

All sorts of ways to build Forth words, named or noname, to act as high order functions
closing over data on stack, in dictionary, in ring buffer, allocated memory (not shown here) or wherever.
--
me

Krishna Myneni

unread,
Oct 25, 2022, 7:00:22 AM10/25/22
to
Yes, but Ruvim's example directly includes the data within the closure
definition. Either way, I don't see a straightforward way of
deallocating both the code and data associated with closures in Forth.

Memory fo closures store in the dictionary should be reclaimable. If we
consider a closure which is local to a word definition, and used within
the word only (this was the example in my original post), then, in
Forth, then code memory may be reclaimed when the word is removed from
the dictionary, e.g. through the use of a MARKER word. For the type of
closure which was used to provide new compilation semantics of a word
(see my recent post on "Demonstration of Dual Semantics" using Gforth's
"[n:d"), when that word is removed from the dictionary (not the one in
which the closure is defined) then the code memory of the closure can be
recovered.

For now, one should probably use closures assuming each new closure
requires persistent memory.

--
Krishna



minf...@arcor.de

unread,
Oct 25, 2022, 8:36:04 AM10/25/22
to
I think that's about it. Last long sleepness night I tinkered around with my Forth
and FWIW got the following to work (residual bugs put aside):

: SET-COUNTER ( n -- loc-adr xt )
{ a }
[[: {{ a }} a . a 1+ to a ;]] ;

10 SET-COUNTER 2dup CLOSURE COUNTER1 CLOSURE COUNTER2
COUNTER1 -> 10
COUNTER1 -> 11
COUNTER2 -> 10

20 SET-COUNTER CLOSURE COUNTER3
COUNTER1 -> 12
COUNTER2 -> 11
COUNTER3 -> 20

Unlike a quotation that yields one single xt, this kind of closure yields
2 outputs: an xt and an address pointer to a static memory template.
The template (incl. copy of the locals stack frame) is created when [[: is
compiled and updated every time when SET-COUNTER is executed.

The word CLOSURE is a CREATE .. DOES> thing that uses the template
to create a new persistent memory record per counter in dictionary.

The ugly implementation behind it uses a second temporary wordlist
for free variables (upper locals). This second wordlist is searched when
the first temporary wordlist of normal local identifiers didn't find a matching
name. IOW although free variables look like locals, they are more like
private VALUEs.

Ruvim

unread,
Oct 28, 2022, 7:49:54 AM10/28/22
to
On 2022-10-23 21:52, Krishna Myneni wrote:
[...]
>
> Forth closures of the type implemented by Anton and Ruvim are found to
> be quite useful when we need to generate an anonymous definition at
> runtime, which binds runtime-computed values.
>
> Ruvim's original definition of PARTIAL1 is state-dependent, i.e. it
> contains an expression of the sort, "STATE @ IF ... THEN." On a
> dual-semantics system it is possible to define PARTIAL1 without any
> reference to STATE as follows (in Gforth):
>
> : partial1 ( x xt1 -- xt2 )
>     2>r :noname r> r> ( -- xt1 x )
>     postpone literal
>     compile,
>     postpone ;
> ;
> compsem:
>     ['] partial1 compile,
>     ]
> ;

Don't want to upset you, but it does not work correctly in Gforth.

This example shows that via "set-compsem" you don't merely define
compilation semantics for a word, but (despite of the name) you specify
a definition that can demonstrate behavior that is not a part of the
compilation semantics.

Namely, if you perform this definition in interpretation state, the
result can vary from the compilation semantics for the word.

In this particular case, when this definition (which was created by
"compsem:") is performed:
- if the system in compilation state, its behavior is to append
"partial1" to the current definition.
- if the system in interpretation state, its behavior is to append
"partial1" to the current definition *and* enters compilation state.

Only the first behavior variant is the compilation semantics for
"partial1" (in the normative notion).
There are no conditions when the second behavior variant can be
demonstrated by the Forth text interpreter when it encounters the word
name "partial1", so it is neither the compilation semantics, nor
interpretation semantics for "partial1".


Moreover, in Gforth, when you execute the xt that "name>compile"
returns, you are not guaranteed that the compilation semantics (i.e.,
the first behavior variant from above in the case of "partial1") will be
performed regardless of the STATE.


A test case:

: p1 [ s" partial1" find-name name>compile execute 123 . ] ;

\ the code in the square brackets should append
\ the execution semantics of "partial1" to "p1",
\ and then print "123".

\ In Gforth it prints nothing.

456 ' . p1 \ It should print nothing and leave xt on the stack.
\ In Gforth it prints "123", leaves xt and enters compilation state.

execute \ It should print "456".
\ In Gforth it prints nothing and
\ tries to append "execute" to the current definition
\ (due to compilation state)


NB: your code doesn't violate the current specifications for
"set-compsem" and "name>compile".

The implementation of "name>compile" in Gforth just effectively imposes
a restriction on programs — a program should perform xt from
"name>compile" only in compilation state to perform the corresponding
compilation semantics (in the general case). This restriction is
similar to one that the popular implementation for "postpone" imposes.


And, as you can see, the dual-xt approach has not allowed to avoid this
problem.



[...]

--
Ruvim

Krishna Myneni

unread,
Oct 28, 2022, 10:21:12 AM10/28/22
to
On 10/28/22 06:49, Ruvim wrote:
> On 2022-10-23 21:52, Krishna Myneni wrote:
> [...]
>>
>> Forth closures of the type implemented by Anton and Ruvim are found to
>> be quite useful when we need to generate an anonymous definition at
>> runtime, which binds runtime-computed values.
>>
>> Ruvim's original definition of PARTIAL1 is state-dependent, i.e. it
>> contains an expression of the sort, "STATE @ IF ... THEN." On a
>> dual-semantics system it is possible to define PARTIAL1 without any
>> reference to STATE as follows (in Gforth):
>>
>> : partial1 ( x xt1 -- xt2 )
>>      2>r :noname r> r> ( -- xt1 x )
>>      postpone literal
>>      compile,
>>      postpone ;
>> ;
>> compsem:
>>      ['] partial1 compile,
>>      ]
>> ;
>
> Don't want to upset you, but it does not work correctly in Gforth.
>

Yes, there is something wrong with the definition. It is not a problem
with Gforth or a problem with NAME>COMPILE.

..
> Moreover, in Gforth, when you execute the xt that "name>compile"
> returns, you are not guaranteed that the compilation semantics (i.e.,
> the first behavior variant from above in the case of "partial1") will be
> performed regardless of the STATE.
>

Yes, Gforth is executing the specified compilation semantics of PARTIAL1
in interpretation state for your example below. Note that the
compilation semantics I specified with COMPSEM: includes a return to
compilation state.

>
> A test case:
>
>   : p1 [ s" partial1" find-name name>compile execute  123 . ] ;
>
>   \ the code in the square brackets should append
>   \ the execution semantics of "partial1" to "p1",
>   \ and then print "123".
> ...

If we look at the compiled definition for P1 in Gforth it shows the
following:

see p1
: p1
[COMPILE] partial1 #123 . ] ; ok

This shows that NAME>COMPILE is indeed doing what it should do. It is
performing the compilation semantics regardless of STATE -- it is just
not the behavior needed for your test case.


>
> NB: your code doesn't violate the current specifications for
> "set-compsem" and "name>compile".
>
> The implementation of "name>compile" in Gforth just effectively imposes
> a restriction on programs — a program should perform xt from
> "name>compile" only in compilation state to perform the corresponding
> compilation semantics (in the general case).

Yes, it appears that this is the case for the example shown.

>  This restriction is
> similar to one that the popular implementation for "postpone" imposes.
>

>
> And, as you can see, the dual-xt approach has not allowed to avoid this
> problem.
>

Not with my example. Is it a fundamental limitation or is my definition
of PARTIAL1 just not the compatible one? I'm not sure.

--
Krishna


Krishna Myneni

unread,
Oct 28, 2022, 11:17:47 AM10/28/22
to
On 10/28/22 09:21, Krishna Myneni wrote:
> On 10/28/22 06:49, Ruvim wrote:
>> On 2022-10-23 21:52, Krishna Myneni wrote:
..
>>>
>>> : partial1 ( x xt1 -- xt2 )
>>>      2>r :noname r> r> ( -- xt1 x )
>>>      postpone literal
>>>      compile,
>>>      postpone ;
>>> ;
>>> compsem:
>>>      ['] partial1 compile,
>>>      ]
>>> ;
>>
>> ... but it does not work correctly in Gforth.
>>
>
> Yes, there is something wrong with the definition. It is not a problem
> with Gforth or a problem with NAME>COMPILE.
>
> ...

>>
>> A test case:
>>
>>    : p1 [ s" partial1" find-name name>compile execute  123 . ] ;
>>
>>    \ the code in the square brackets should append
>>    \ the execution semantics of "partial1" to "p1",
>>    \ and then print "123".
>> ...
>
> If we look at the compiled definition for P1 in Gforth it shows the
> following:
>
> see p1
> : p1
>   [COMPILE] partial1 #123 . ] ; ok
>
> This shows that NAME>COMPILE is indeed doing what it should do. It is
> performing the compilation semantics regardless of STATE -- it is just
> not the behavior needed for your test case.
>
..

The lesson I take away from your example is that dual-semantics words
should not change the STATE except that compilation semantics may
include a sequence such as "[ ... ]". In my dual-semantics definition of
PARTIAL1 the compilation semantics includes "]" -- state is explicitly
being changed back to .

The following dual-semantics definition does work with your test case
and also with the original test cases:


: partial1 ( x xt1 -- xt2 )
2>r :noname r> r> ( -- xt1 x )
postpone literal
compile,
postpone ;
;
compsem: [ ' partial1 ] literal compile, ;


: p1 [ s" partial1" find-name name>compile execute 123 . ] ; 123 ok
see p1
: p1
[COMPILE] partial1 ; ok

: n^2+ [: dup * + ;] partial1 ; ok
5 n^2+ constant 5^2+ ok
6 5^2+ execute . 31 ok

It thus appears that a correct dual-semantics definition can solve the
problem.

--
Krishna





Ruvim

unread,
Oct 28, 2022, 11:30:24 AM10/28/22
to
On 2022-10-28 14:21, Krishna Myneni wrote:
> On 10/28/22 06:49, Ruvim wrote:
[...]

>> Moreover, in Gforth, when you execute the xt that "name>compile"
>> returns, you are not guaranteed that the compilation semantics (i.e.,
>> the first behavior variant from above in the case of "partial1") will
>> be performed regardless of the STATE.
>>
>
> Yes, Gforth is executing the specified compilation semantics of PARTIAL1
> in interpretation state for your example below. Note that the
> compilation semantics I specified with COMPSEM: includes a return to
> compilation state.
>

"Enter compilation state" cannot be a part of compilation semantics at all.

"compsem:" allows you to specify a definition that can demonstrate
behavior that cannot be a part of any compilation semantics in principle.


>>
>> A test case:
>>
>>    : p1 [ s" partial1" find-name name>compile execute  123 . ] ;
>>
>>    \ the code in the square brackets should append
>>    \ the execution semantics of "partial1" to "p1",
>>    \ and then print "123".
>> ...
>
> If we look at the compiled definition for P1 in Gforth it shows the
> following:
>
> see p1
> : p1
>   [COMPILE] partial1 #123 . ] ; ok
>
> This shows that NAME>COMPILE is indeed doing what it should do. It is
> performing the compilation semantics regardless of STATE -- it is just
> not the behavior needed for your test case.

Let's try to perform the returned xt in compilation state:

: ]execute[ ( i*x xt -- j*x ) ] execute postpone [ ; immediate
: p1 [ s" partial1" find-name name>compile ]execute[ 123 . ] ;
see p1

| : p1
| [COMPILE] partial1 ;

The result is different. It means, the behavior of the returned xt
depends on STATE.




>>
>> NB: your code doesn't violate the current specifications for
>> "set-compsem" and "name>compile".
>>
>> The implementation of "name>compile" in Gforth just effectively
>> imposes a restriction on programs — a program should perform xt from
>> "name>compile" only in compilation state to perform the corresponding
>> compilation semantics (in the general case).
>
> Yes, it appears that this is the case for the example shown.
>
>>   This restriction is similar to one that the popular implementation
>> for "postpone" imposes.
>>
>
>>
>> And, as you can see, the dual-xt approach has not allowed to avoid
>> this problem.
>>
>
> Not with my example. Is it a fundamental limitation or is my definition
> of PARTIAL1 just not the compatible one? I'm not sure.

I don't see any incompatibility with "compsem:" or "name>compile".


But, the specification for "compsem:" can confuse:

| Changes the compilation semantics of the current definition to
| perform the definition starting at the "compsem:"

"To perform" does not mean that the compilation semantics are identical
to the execution semantics of the definition starting at the "compsem:".

Apart from a not normative notion of "compilation semantics", of course.


A more tidy wording:
Make the compilation semantics of the most recent definition to perform
in compilation state the execution semantics of the definition starting
at the "compsem:".

Or even this:
Make the compilation semantics of the most recent definition to be
equivalent to perform in compilation state the execution semantics of
the definition starting at the "compsem:".



--
Ruvim

Krishna Myneni

unread,
Oct 28, 2022, 12:40:02 PM10/28/22
to
On 10/28/22 10:30, Ruvim wrote:


The problem stems from your original definition of PARTIAL1 :

: partial1 ( x xt1 -- xt2 )
\ create a partially applied definition xt2
\ by fixing the top argument for xt1
state @ >r
2>r :noname r> r> lit, compile, postpone ;
r> if ] then
;

There was no need for you to bring STATE into this definition. The
following works just as well for all subsequent definitions using
PARTIAL1, including your closures.

: partial1 ( x xt1 -- xt2 )
2>r :noname r> r> postpone literal compile, postpone ;
; ok


: foo [: dup * + ;] partial1 ; ok
2 foo constant f2 ok
3 foo constant f3 ok
0 f2 execute . 4 ok
1 f2 execute . 5 ok
0 f3 execute . 9 ok
1 f3 execute . 10 ok
2 f3 execute . 11 ok

: cl[n:d postpone [: ; immediate ok
: ]cl postpone ;] postpone partial1 ; immediate ok
: n^2+ cl[n:d dup * + ]cl ; ok
2 n^2+ constant f2


: p1 [ s" partial1" find-name name>compile execute 123 . ] ; 123 ok
see p1
: p1
partial1 ; ok

--
Krishna


Krishna Myneni

unread,
Oct 28, 2022, 7:16:20 PM10/28/22
to
On 10/28/22 11:39, Krishna Myneni wrote:
> On 10/28/22 10:30, Ruvim wrote:
>
>
> The problem stems from your original definition of PARTIAL1 :
>
>   : partial1 ( x xt1 -- xt2 )
>     \ create a partially applied definition xt2
>     \ by fixing the top argument for xt1
>     state @ >r
>     2>r :noname r> r> lit, compile, postpone ;
>     r> if ] then
>   ;
>
> There was no need for you to bring STATE into this definition. The
> following works just as well for all subsequent definitions using
> PARTIAL1, including your closures.
>
> : partial1 ( x xt1 -- xt2 )
>     2>r :noname r> r> postpone literal compile, postpone ;
> ;  ok
>
..

This was a useful exercise. It allowed me to fix an issue in kForth,
which didn't allow me to use COMPILE-NAME (or equivalently,
"NAME>COMPILE EXECUTE") in interpretation state, following Ruvim's test.
The latest update to kForth-64 on Github fixes this issue.

Update for kForth-32 to follow soon.

--
Krishna

----
include ans-words

/home/krishna/kforth/ans-words.4th
ok
include ssd

/home/krishna/kforth/ssd.4th

/home/krishna/kforth/struct-200x.4th

/home/krishna/kforth/modules.4th

/home/krishna/kforth/dump.4th
ok
ok
3 n^2+ constant f3
ok
0 f2 execute .
4 ok
1 f2 execute .
5 ok
0 f3 execute .
9 ok
1 f3 execute .
10 ok
2 f3 execute .
11 ok
ok
: p1 [ s" partial1" find-name name>compile execute 123 . ] ;
123 ok
see p1
55D3E22E3240 PARTIAL1
55D3E22E3249 RET
ok
-----

Ruvim

unread,
Oct 29, 2022, 4:19:31 AM10/29/22
to
On 2022-10-28 16:39, Krishna Myneni wrote:
> On 10/28/22 10:30, Ruvim wrote:
[...]
>
> The problem stems from your original definition of PARTIAL1 :
>
>   : partial1 ( x xt1 -- xt2 )
>     \ create a partially applied definition xt2
>     \ by fixing the top argument for xt1
>     state @ >r
>     2>r :noname r> r> lit, compile, postpone ;
>     r> if ] then
>   ;

This my original definition does not have that problem.

Your system is able to create new definitions during compiling of
another definition. So I just tried to employ this feature.

As I can see now, to make it work ":noname" should be executed in
interpretation state only. The word "execute-interpretively" is intended
for that.

A corrected version:

[undefined] lit, [if] : lit, ( x -- ) postpone literal ; [then]

: compilation ( -- flag ) state @ 0<> ;
: leave-compilation ( -- ) postpone [ ;
: enter-compilation ( -- ) ] ;

: execute-interpretively ( i*x xt -- j*x )
compilation 0= if execute exit then
leave-compilation execute enter-compilation
;

: partial1 ( x xt1 -- xt2 )
[: 2>r :noname r> r> lit, compile, postpone ; ;]
execute-interpretively
;


A test case

: [bar] 123 ['] . partial1 compile, ; immediate

: baz [bar] 456 . ;

baz \ should print "123 456"


This test will not work on some standard system, since it has an
environmental dependency on creation definitions while compiling another
definition. kForth complies to this dependency.



NB: "partial1" is an ordinary word.

This example also shows that the dual-xt approach cannot help us to
avoid STATE-dependent behavior (namely, STATE-dependent execution
semantics).




>
> There was no need for you to bring STATE into this definition. The
> following works just as well for all subsequent definitions using
> PARTIAL1, including your closures.
>
> : partial1 ( x xt1 -- xt2 )
>     2>r :noname r> r> postpone literal compile, postpone ;
> ;


It does not work in another test (see above).



--
Ruvim

Krishna Myneni

unread,
Oct 29, 2022, 7:28:40 AM10/29/22
to
On 10/29/22 03:19, Ruvim wrote:
> On 2022-10-28 16:39, Krishna Myneni wrote:

> A test case
>
>   : [bar]  123 ['] . partial1  compile, ; immediate
>
>   : baz [bar] 456 . ;
>
>   baz \ should print "123 456"
>
>
> This test will not work on some standard system, since it has an
> environmental dependency on creation definitions while compiling another
> definition. kForth complies to this dependency.
>
..
>>
>> : partial1 ( x xt1 -- xt2 )
>>      2>r :noname r> r> postpone literal compile, postpone ;
>> ;
>
>
> It does not work in another test (see above).
>
>

The simpler definition of PARTIAL1 works fine under kForth. Your test,

: [bar] 123 ['] . partial1 compile, ; immediate

can be simplified to be

: bar 123 ['] partial1 compile, ;
: baz [ bar ] 456 . ;

baz
123 456 ok

There is no need for all of the contortions of EXECUTE-INTERPRETIVELY
and the STATE dependence of PARTIAL1 if a system allows

: NAME ... [ :NONAME ... ; ] ... ;

--
Krishna


Ruvim

unread,
Oct 29, 2022, 8:30:00 AM10/29/22
to
On 2022-10-29 11:28, Krishna Myneni wrote:
> On 10/29/22 03:19, Ruvim wrote:
>> On 2022-10-28 16:39, Krishna Myneni wrote:
>
>> A test case
>>
>>    : [bar]  123 ['] . partial1  compile, ; immediate
>>
>>    : baz [bar] 456 . ;
>>
>>    baz \ should print "123 456"
>>
>>
>> This test will not work on some standard system, since it has an
>> environmental dependency on creation definitions while compiling
>> another definition. kForth complies to this dependency.
>>
> ..
>>>
>>> : partial1 ( x xt1 -- xt2 )
>>>      2>r :noname r> r> postpone literal compile, postpone ;
>>> ;
>>
>>
>> It does not work in another test (see above).
>>
>>
>
> The simpler definition of PARTIAL1 works fine under kForth.

It does not work in the above test in kForth:

: baz [bar] 456 . ;
Line 9: VM Error(-276): End of definition with no beginning

But my more complex definition of "partial1" works.


> Your test,
>
> : [bar]  123 ['] . partial1  compile, ; immediate
>
> can be simplified to be
>
> : bar 123 ['] partial1 compile, ;
> : baz [ bar ] 456 . ;
>
> baz
> 123 456  ok
>

A test purpose is not to solve a practical task, but to check how a
system or program behaves in some edge cases.


The word "partial1", as it was initially defined by me, has a
restriction in a standard program: it cannot be performed if the current
definition exists (due to 3.4.5).

It's OK if you can live with this restriction. But if a system already
implements the corresponding capabilities under the hood, I would prefer
to avoid this restriction, than document/explain it to users.


BTW, it's possible to define "partial1" without this restriction in a
standard-compliant way, but it will be significant larger and slightly
less efficient in run-time.



> There is no need for all of the contortions of EXECUTE-INTERPRETIVELY
> and the STATE dependence of PARTIAL1 if a system allows
>
> : NAME ... [ :NONAME ... ; ] ... ;


Well, it looks like you admit that the dual-xt approach does not provide
any special means to define a more advanced "partial1".



--
Ruvim

Krishna Myneni

unread,
Oct 29, 2022, 12:03:54 PM10/29/22
to
All I am saying is that the simple definition of PARTIAL1 which has no
reference to STATE suits our purpose at hand, e.g. for closures. It does
so regardless of whether or not a system adheres to 3.4.5 of the
standard. The simple definition of PARTIAL1 will not pass your most
recent test.

I simply point out that your test of [BAR] is no drawback for systems
which do violate 3.4.5 and allow execution of :NONAME while the
compilation of the current definition is suspended. We can obtain the
same effect of the test by using "[" and "]" to change states.

While the purpose of your test is clear, the point of implementing and
testing words is to achieve some goal(s) in the simplest and most
consistent manner which can meet the goal(s). You are adding an
extraordinary amount of complexity to allow for PARTIAL1 to be used in
all possible corner cases -- this is simply not a practical approach
because such complexity will be avoided by programmers.

--
Krishna


S Jack

unread,
Oct 29, 2022, 1:33:15 PM10/29/22
to
On Monday, October 24, 2022 at 9:43:32 PM UTC-5, S Jack wrote:

In playing around with various forms having closure I found one
interesting: higher-order defining word

A defining word that creates an xt. The xt is created at the time the
defining words creates a word. The xt provides the primary action. The
created word provides maintenance of the created word's data. In the
example below the created word's data is a count and a saved copy of
the created xt. The main action is the xt that performs countdown.

: countdown:
create 0 , here 0 ,
"[: dat @ 0> if -1 dat +! fi ;]" eval dup rot !
does >r
case
store of R> ! endof \ store n in counter
xt of R> cell+ @ endof \ return the created xt
show of R> ? endof \ print the counter
R> abort
endcase ;

defer countdown
countdown: cd1 is countdown \ created xt assigned to deferred word
countdown: cd2 drop \ created xt discarded (but still exists)
42 store cd2
666 store cd1
i. show cd1 ==> 666
countdown
countdown
i. show cd1 ==> 664
xt cd2 is countdown \ new assignment to the deferred word
countdown
countdown
i. show cd2 ==> 40
xt cd1 is countdown \ deferred word re-assinged first xt
countdown
i. show cd1 ==> 663
xt cd1 enter \ first xt executed directly
i. show cd1 ==> 662

-fin- ok

Notes:
i. [: ... ;] is an out-of-flow quotation
Like a quotation an xt is created but it's creation is done
outside of the main flow of action nor is it linked into the
dictionary's word list.
i. DAT is the compiled address of latest word's data area.
i. STORE XT SHOW are id's that return the address of their name field
store id. ==> store
--
me

Ruvim

unread,
Nov 2, 2022, 3:11:46 PM11/2/22
to
On 2022-10-29 16:03, Krishna Myneni wrote:
> On 10/29/22 07:29, Ruvim wrote:
[...]
>> The word "partial1", as it was initially defined by me, has a
>> restriction in a standard program: it cannot be performed if the
>> current definition exists (due to 3.4.5).
>>
>> It's OK if you can live with this restriction. But if a system already
>> implements the corresponding capabilities under the hood, I would
>> prefer to avoid this restriction, than document/explain it to users.
[...]

>>> There is no need for all of the contortions of EXECUTE-INTERPRETIVELY
>>> and the STATE dependence of PARTIAL1 if a system allows
>>>
>>> : NAME ... [ :NONAME ... ; ] ... ;
>>
>>
>> Well, it looks like you admit that the dual-xt approach does not
>> provide any special means to define a more advanced "partial1".
>>
>
> All I am saying is that the simple definition of PARTIAL1 which has no
> reference to STATE suits our purpose at hand, e.g. for closures. It does
> so regardless of whether or not a system adheres to 3.4.5 of the
> standard. The simple definition of PARTIAL1 will not pass your most
> recent test.
>
> I simply point out that your test of [BAR] is no drawback for systems
> which do violate 3.4.5 and allow execution of :NONAME while the
> compilation of the current definition is suspended.

Just a side note.
The section "3.4.5" does not restrict a system, but only a program.

During compilation of a definition, a system may use data space at it's
own discretion.

And if your system provides an advanced "partial1" — it does not violate
"3.4.5".


[...]

> You are adding an
> extraordinary amount of complexity to allow for PARTIAL1 to be used in
> all possible corner cases -- this is simply not a practical approach
> because such complexity will be avoided by programmers.

I can't agree with that, but agree with Anton's formulation: "As a
system implementor, you may be tempted to cut corners, but that means
you also cut the corner cases, and users of your system will get
unpleasant surprises"[1]


[1]
https://github.com/ForthHub/discussion/discussions/105#discussioncomment-3715333

--
Ruvim

Krishna Myneni

unread,
Nov 3, 2022, 9:03:23 AM11/3/22
to
I never claimed that a system in which a "first class" PARTIAL1 may be
written will violate 3.4.5. My claim is that a system which permits a
:NONAME definition nested within a definition can support an advanced
PARTIAL1 -- the two are different claims.

>
> [...]
>
>> You are adding an extraordinary amount of complexity to allow for
>> PARTIAL1 to be used in all possible corner cases -- this is simply not
>> a practical approach because such complexity will be avoided by
>> programmers.
>
> I can't agree with that, but agree with Anton's formulation: "As a
> system implementor, you may be tempted to cut corners, but that means
> you also cut the corner cases, and users of your system will get
> unpleasant surprises"[1]
>

My argument is not against writing a first class PARTIAL1 which works as
expected using POSTPONE '(tick) NAME>COMPILE etc. My issue is
understanding what may be structurally wrong with the design of a Forth
system which makes writing a first class PARTIAL1 a complex task.
Dual-semantics systems go a long way towards simplifying the definition
of first class words. It looks like implementing a first class PARTIAL1
in a dual-semantics systems may still be complex. So what's the
underlying problem, or, to put it another way, what is required of the
Forth system design to enable the simple definition of such a word ?

--
Krishna



Ruvim

unread,
Nov 4, 2022, 8:50:06 AM11/4/22
to
On 2022-11-03 13:03, Krishna Myneni wrote:
> On 11/2/22 14:11, Ruvim wrote:
>> On 2022-10-29 16:03, Krishna Myneni wrote:
>>> On 10/29/22 07:29, Ruvim wrote:
>> [...]
>>>>> There is no need for all of the contortions of
>>>>> EXECUTE-INTERPRETIVELY and the STATE dependence of PARTIAL1 if a
>>>>> system allows
>>>>>
>>>>> : NAME ... [ :NONAME ... ; ] ... ;
>>>>
>>>>
>>>> Well, it looks like you admit that the dual-xt approach does not
>>>> provide any special means to define a more advanced "partial1".
>>>>
>>>
>>> All I am saying is that the simple definition of PARTIAL1 which has
>>> no reference to STATE suits our purpose at hand, e.g. for closures.
>>> It does so regardless of whether or not a system adheres to 3.4.5 of
>>> the standard. The simple definition of PARTIAL1 will not pass your
>>> most recent test.
>>>
>>> I simply point out that your test of [BAR] is no drawback for systems
>>> which do violate 3.4.5 and allow execution of :NONAME while the
>>> compilation of the current definition is suspended.
>>
>> Just a side note.
>> The section "3.4.5" does not restrict a system, but only a program.
>>
>> During compilation of a definition, a system may use data space at
>> it's own discretion.
>>
>> And if your system provides an advanced "partial1" — it does not
>> violate "3.4.5".
>>
>
> I never claimed that a system in which a "first class" PARTIAL1 may be
> written will violate 3.4.5.


My commented is for these fragments:

- "whether or not a system adheres to 3.4.5"
- "for systems which do violate 3.4.5"

No one system can violate 3.4.5 since this section does not restrict
systems at all.



> My claim is that a system which permits a
> :NONAME definition nested within a definition can support an advanced
> PARTIAL1

Agreed.


> -- the two are different claims.




>>
>> [...]
>>
>>> You are adding an extraordinary amount of complexity to allow for
>>> PARTIAL1 to be used in all possible corner cases -- this is simply
>>> not a practical approach because such complexity will be avoided by
>>> programmers.
>>
>> I can't agree with that, but agree with Anton's formulation: "As a
>> system implementor, you may be tempted to cut corners, but that means
>> you also cut the corner cases, and users of your system will get
>> unpleasant surprises"[1]
>>
>
> My argument is not against writing a first class PARTIAL1 which works as
> expected using POSTPONE '(tick) NAME>COMPILE etc.

> My issue is
> understanding what may be structurally wrong with the design of a Forth
> system which makes writing a first class PARTIAL1 a complex task.
> Dual-semantics systems go a long way towards simplifying the definition
> of first class words. It looks like implementing a first class PARTIAL1
> in a dual-semantics systems may still be complex. So what's the
> underlying problem, or, to put it another way, what is required of the
> Forth system design to enable the simple definition of such a word ?


I don't think that implementing "partial1" via "execute-interpretively"
is a complex task.


Nevertheless, to implement it without "execute-interpretively", we
should have a plumbing word set to create definitions (see also my post
[1]).

In my vocabulary some of these plumbing words are [2]:

conceive ( -- ) ( C: -- colon-sys )
\ Start compilation of a new definition.

germ ( -- xt|0 )
\ Return the execution token xt of the current definition,
\ or 0 if such a definition is absent.

birth ( -- xt ) ( C: colon-sys -- )
\ End compilation of the current definition,
\ return its execution token xt.

NB: These words shall work correctly regardless whether the current
definition is absent or present. Also, they don't enter into compilation
state or interpretation state.

The term "current definition" is updated:

*current definition*: the definition whose compilation has been started
most recently among those whose compilation hasn't been ended.


Using these words, an implementation for an advanced "partial1" becomes
very simple:

: partial1 ( x xt1 -- xt2 )
2>r conceive r> r> lit, compile, birth
;



[1] Re: Naming for parsing words, 2022-05-29, 20:48Z
news://t70m6s$cbt$1...@dont-email.me
https://groups.google.com/g/comp.lang.forth/c/6NKrBk2sh38/m/sgyW20zfAgAJ


[2] Obtaining the xt of the current definition
https://gist.github.com/ruv/e04e29c494d7dc8fc9cb1641005dc3e4


--
Ruvim
0 new messages