• Closures in kForth

    From Krishna Myneni@21:1/5 to All on Sun Oct 16 14:54:57 2022
    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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From minforth@arcor.de@21:1/5 to Krishna Myneni on Sun Oct 16 22:56:22 2022
    Krishna Myneni schrieb am Sonntag, 16. Oktober 2022 um 21:54:59 UTC+2:
    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.


    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?

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Marcel Hendrix@21:1/5 to minf...@arcor.de on Mon Oct 17 00:05:00 2022
    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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Anton Ertl@21:1/5 to Krishna Myneni on Mon Oct 17 07:47:24 2022
    Krishna Myneni <krishna.myneni@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

    - 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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From minforth@arcor.de@21:1/5 to Marcel Hendrix on Mon Oct 17 00:45:59 2022
    Marcel Hendrix schrieb am Montag, 17. Oktober 2022 um 09:05:02 UTC+2:
    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.

    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.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Anton Ertl@21:1/5 to minf...@arcor.de on Mon Oct 17 06:50:07 2022
    "minf...@arcor.de" <minforth@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
    execute . \ 1
    execute . \ 2
    3 >x execute
    4 >y execute
    execute . \ 3
    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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Ruvim@21:1/5 to Krishna Myneni on Mon Oct 17 09:50:18 2022
    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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Krishna Myneni@21:1/5 to Anton Ertl on Mon Oct 17 06:36:15 2022
    On 10/17/22 02:47, Anton Ertl wrote:
    Krishna Myneni <krishna.myneni@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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Krishna Myneni@21:1/5 to Ruvim on Mon Oct 17 06:41:06 2022
    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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Ruvim@21:1/5 to Krishna Myneni on Mon Oct 17 16:24:41 2022
    On 2022-10-17 11:41, Krishna Myneni wrote:
    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.

    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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Krishna Myneni@21:1/5 to Ruvim on Mon Oct 17 15:14:50 2022
    On 10/17/22 11:24, Ruvim wrote:
    On 2022-10-17 11:41, Krishna Myneni wrote:
    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.


    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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Ruvim@21:1/5 to Krishna Myneni on Tue Oct 18 01:06:30 2022
    On 2022-10-17 20:14, Krishna Myneni wrote:
    On 10/17/22 11:24, Ruvim wrote:
    On 2022-10-17 11:41, Krishna Myneni wrote:
    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.


    I tried your definition of partial1 (substituting POSTPONE LITERAL for "LIT,") and it works as advertised in kForth. Cast into Anton's example,

    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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From S Jack@21:1/5 to Anton Ertl on Tue Oct 18 06:03:12 2022
    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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From minforth@arcor.de@21:1/5 to km361...@gmail.com on Tue Oct 18 07:48:12 2022
    km361...@gmail.com schrieb am Dienstag, 18. Oktober 2022 um 16:15:33 UTC+2:
    On Tuesday, October 18, 2022 at 8:03:25 AM UTC-5, S Jack wrote:
    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
    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.


    From a broader perspective: https://stackoverflow.com/questions/220658/what-is-the-difference-between-a-closure-and-a-lambda/36878651#36878651

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Krishna Myneni@21:1/5 to S Jack on Tue Oct 18 07:15:32 2022
    On Tuesday, October 18, 2022 at 8:03:25 AM UTC-5, S Jack wrote:
    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
    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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From S Jack@21:1/5 to km361...@gmail.com on Tue Oct 18 09:12:47 2022
    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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From S Jack@21:1/5 to S Jack on Tue Oct 18 09:29:14 2022
    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:

    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

    An "escaping closure" may be a little more involvd.
    --
    me

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Krishna Myneni@21:1/5 to S Jack on Tue Oct 18 13:54:57 2022
    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."

    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).


    I amend my statement: "I probably don't understand closures."

    The link by minf is useful.

    --
    Krishna

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From S Jack@21:1/5 to All on Tue Oct 18 21:47:01 2022
    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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From minforth@arcor.de@21:1/5 to km361...@gmail.com on Wed Oct 19 00:07:51 2022
    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."

    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).

    I amend my statement: "I probably don't understand closures."

    The link by minf is useful.


    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...

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From minforth@arcor.de@21:1/5 to minf...@arcor.de on Wed Oct 19 01:01:42 2022
    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... ;-)

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From minforth@arcor.de@21:1/5 to minf...@arcor.de on Wed Oct 19 01:22:57 2022
    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 ...

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Paul Rubin@21:1/5 to minf...@arcor.de on Wed Oct 19 02:49:01 2022
    "minf...@arcor.de" <minforth@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.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From minforth@arcor.de@21:1/5 to Paul Rubin on Wed Oct 19 05:09:53 2022
    Paul Rubin schrieb am Mittwoch, 19. Oktober 2022 um 11:49:04 UTC+2:
    "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.

    To make it clearer (with the BASE variable here instead of some VALUE):

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

    Yes or no?

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From S Jack@21:1/5 to minf...@arcor.de on Wed Oct 19 06:31:37 2022
    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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From dxforth@21:1/5 to S Jack on Thu Oct 20 02:37:50 2022
    On 20/10/2022 12:31 am, S Jack wrote:
    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.)

    ANS-Forth is not amused.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From S Jack@21:1/5 to All on Wed Oct 19 08:32:09 2022
    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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From S Jack@21:1/5 to S Jack on Wed Oct 19 11:28:55 2022
    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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Paul Rubin@21:1/5 to minf...@arcor.de on Wed Oct 19 11:22:00 2022
    "minf...@arcor.de" <minforth@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).

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From minforth@arcor.de@21:1/5 to Paul Rubin on Wed Oct 19 14:20:56 2022
    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?

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Krishna Myneni@21:1/5 to minf...@arcor.de on Wed Oct 19 17:53:40 2022
    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.

    --
    Krishna

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From S Jack@21:1/5 to S Jack on Thu Oct 20 08:37:40 2022
    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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From minforth@arcor.de@21:1/5 to Krishna Myneni on Sat Oct 22 05:26:41 2022
    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.

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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Krishna Myneni@21:1/5 to minf...@arcor.de on Sun Oct 23 16:52:47 2022
    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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Paul Rubin@21:1/5 to Krishna Myneni on Sun Oct 23 17:21:25 2022
    Krishna Myneni <krishna.myneni@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.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From minforth@arcor.de@21:1/5 to Krishna Myneni on Mon Oct 24 00:33:58 2022
    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.. ;-)

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From minforth@arcor.de@21:1/5 to Krishna Myneni on Mon Oct 24 01:50:46 2022
    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).

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From S Jack@21:1/5 to All on Mon Oct 24 19:27:02 2022
    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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From S Jack@21:1/5 to All on Mon Oct 24 19:43:30 2022
    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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Krishna Myneni@21:1/5 to Paul Rubin on Tue Oct 25 06:00:20 2022
    On 10/23/22 19:21, Paul Rubin wrote:
    Krishna Myneni <krishna.myneni@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.

    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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From minforth@arcor.de@21:1/5 to Krishna Myneni on Tue Oct 25 05:36:03 2022
    Krishna Myneni schrieb am Dienstag, 25. Oktober 2022 um 13:00:22 UTC+2:
    On 10/23/22 19:21, Paul Rubin wrote:
    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.
    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.

    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.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From S Jack@21:1/5 to All on Sat Oct 29 10:33:13 2022
    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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)