• Jensen's Device

    From anton@anton@mips.complang.tuwien.ac.at (Anton Ertl) to comp.lang.forth on Sat Mar 16 08:44:08 2024
    From Newsgroup: comp.lang.forth

    Jensen's device <https://en.wikipedia.org/wiki/Jensen%27s_device>
    demonstrates capabilities of Algol 60's call-by-name that later
    programming languages have provided through other means. <https://rosettacode.org/wiki/Jensen's_Device> shows solutions for
    many other programming languages, including Forth.

    The rosettacode task includes just one call to sum:

    sum (i, 1, 100, 1/i)

    This makes it easy to implement something that is not as general as
    the Algol 60 version. This is shown particularly by the first Forth
    version <https://rosettacode.org/wiki/Jensen's_Device#Forth>:

    : sum 0 s>f 1+ swap ?do i over execute f+ loop drop ;
    :noname s>f 1 s>f fswap f/ ; 1 100 sum f.

    It passes i on the stack. How would one use this SUM for the
    following call given by wikipedia:

    Sum(i, l, m, Sum(j, l, n, A[i,j]))

    The second Forth version is as follows:

    fvariable ii \ i is a Forth word that we need
    : sum ( xt1 lo hi xt2 -- r )
    0e swap 1+ rot ?do ( addr xt r1 )
    i s>f over execute f! dup execute f+
    loop 2drop ;
    ' ii 1 100 :noname 1e ii f@ f/ ; sum f.

    This is in many respects very close to the Algol version. II is a
    global variable, but given the way that the first parameter of SUM is
    used (just as a memory location for storing and accessing the loop
    index), that does not pose problems; the use of a global variable
    eliminates the need to deal with scoping.

    II is also an FP variable (the Algol 60 version uses integer), but one
    could just as well use a cell instead:

    variable ii \ i is a Forth word that we need
    : sum ( xt1 lo hi xt2 -- r )
    0e swap 1+ rot ?do ( r1 xt1 xt2 )
    i 2 pick execute ! dup execute f+
    loop 2drop ;
    ' ii 1 100 :noname 1e ii @ s>f f/ ; sum f.

    I have written a version using Gforth (development) features to allow
    using local storage (where the memory for II/I1 is reclaimed at the
    end of MAIN):

    : sum {: xt: i1 lo hi xt: term -- r :}
    \ stack effects: i1 ( -- addr ); term ( -- r1 )
    0e hi 1+ lo +do
    i i1 ! term f+
    loop ;

    : main ( -- )
    0 {: w^ i1 :} i1 [n:l ;] 1 100 i1 [n:l @ s>f 1/f ;] sum f. ;

    main

    The SUM code does exactly the same things as in the second Forth
    version above, but the implementation uses locals, including
    defer-flavoured locals I1 and TERM (indicated by the XT: before the
    local definition). I think the resulting code is easier to follow,
    but I am sure others will disagree.

    The more insteresting part is in MAIN. Here we create a
    variable-flavoured local I1 (alternatively, one could create a
    cell-sized buffer) as a replacement for the global II. To bind I1 in
    the xts passed to SUM, this code uses pure-stack closures: The stack
    effects involved are:

    ( addr ) [n:l ( addr ) ;] ( xt < -- addr > )
    ( addr ) [n:l ( addr ) @ s>f 1/f ( r ) ;] ( xt < -- r > )

    So these pure-stack closures take the I1 address from the data stack
    at closure construction time and push it on the data stack at closure
    execution time. The N in [N:L indicates that one cell is involved,
    the L indicates that the memory for the closure has the lifetime of
    locals (in this case until the end of MAIN).

    The C entry points out that macros are close to call-by-name
    semantics. Let's see how that works out in Gforth:

    : ]sum ( compile-time: ) {: xt: i1 xt: term -- :}
    \ run-time: ( lo hi -- r )
    ] ]] 0e 1+ swap +do
    i i1 ! term f+
    loop [[ ; immediate

    variable i1
    : term1 i1 @ s>f 1/f ;
    : main2 ( -- )
    [ ' i1 ] 1 100 [ ' term1 ]sum f. ;

    main2 cr

    Here main2 is decompiled as:

    : main2 #1 #100
    0.00000000000000E0 1+ swap
    +do i i1 ! term1 f+
    loop
    f. ; ok

    This variant reverts to using a global variable I1, because the macro
    is expanded at compile-time and dealing with run-time locals in macros
    is an unsolved problem in Gforth. It uses a named definition TERM1
    rather than a quotation for better readability of the decompiler
    output. The code of the macro is remarkably similar to the code of
    the Gforth SUM above, thanks to ]] ... [[, and also the way
    POSTPONE/]] treats defer-flavoured locals (it COMPILE,s them).

    - 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 2023: https://euro.theforth.net/2023
    --- Synchronet 3.20a-Linux NewsLink 1.114
  • From minforth@minforth@gmx.net (minforth) to comp.lang.forth on Sat Mar 16 22:12:11 2024
    From Newsgroup: comp.lang.forth

    . can't remember who called Forth a write-only language .. ;-)

    btw impressive examples!
    --- Synchronet 3.20a-Linux NewsLink 1.114
  • From anton@anton@mips.complang.tuwien.ac.at (Anton Ertl) to comp.lang.forth on Sun Mar 17 08:39:28 2024
    From Newsgroup: comp.lang.forth

    anton@mips.complang.tuwien.ac.at (Anton Ertl) writes:
    Jensen's device <https://en.wikipedia.org/wiki/Jensen%27s_device> >demonstrates capabilities of Algol 60's call-by-name that later
    programming languages have provided through other means. ><https://rosettacode.org/wiki/Jensen's_Device> shows solutions for
    many other programming languages, including Forth.

    The rosettacode task includes just one call to sum:

    sum (i, 1, 100, 1/i)

    This makes it easy to implement something that is not as general as
    the Algol 60 version. This is shown particularly by the first Forth
    version <https://rosettacode.org/wiki/Jensen's_Device#Forth>:

    : sum 0 s>f 1+ swap ?do i over execute f+ loop drop ;
    :noname s>f 1 s>f fswap f/ ; 1 100 sum f.

    It passes i on the stack. How would one use this SUM for the
    following call given by wikipedia:

    Sum(i, l, m, Sum(j, l, n, A[i,j]))

    The second Forth version is as follows:

    fvariable ii \ i is a Forth word that we need
    : sum ( xt1 lo hi xt2 -- r )
    0e swap 1+ rot ?do ( addr xt r1 )
    i s>f over execute f! dup execute f+
    loop 2drop ;
    ' ii 1 100 :noname 1e ii f@ f/ ; sum f.

    Looking at the history page, I find that the second version is by me,
    written 10 years ago, and discussed here <2014Feb14.124846@mips.complang.tuwien.ac.at> ff., and that the first
    version is by Hans Bezemer.

    - 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 2023: https://euro.theforth.net/2023
    --- Synchronet 3.20a-Linux NewsLink 1.114
  • From anton@anton@mips.complang.tuwien.ac.at (Anton Ertl) to comp.lang.forth on Sun Mar 17 08:59:17 2024
    From Newsgroup: comp.lang.forth

    anton@mips.complang.tuwien.ac.at (Anton Ertl) writes:
    The C entry points out that macros are close to call-by-name
    semantics. Let's see how that works out in Gforth:

    : ]sum ( compile-time: ) {: xt: i1 xt: term -- :}
    \ run-time: ( lo hi -- r )
    ] ]] 0e 1+ swap +do
    i i1 ! term f+
    loop [[ ; immediate

    variable i1
    : term1 i1 @ s>f 1/f ;
    : main2 ( -- )
    [ ' i1 ] 1 100 [ ' term1 ]sum f. ;

    main2 cr

    Here main2 is decompiled as:

    : main2 #1 #100
    0.00000000000000E0 1+ swap
    +do i i1 ! term1 f+
    loop
    f. ; ok

    This variant reverts to using a global variable I1, because the macro
    is expanded at compile-time and dealing with run-time locals in macros
    is an unsolved problem in Gforth. It uses a named definition TERM1
    rather than a quotation for better readability of the decompiler
    output. The code of the macro is remarkably similar to the code of
    the Gforth SUM above, thanks to ]] ... [[, and also the way
    POSTPONE/]] treats defer-flavoured locals (it COMPILE,s them).

    A more idiomatic macro-based approach is as follows:

    : sum< ( run-time: hi+1 lo -- 0e )
    0e postpone ?do ; immediate

    : >sum ( run-time: r1 r2 -- r3 )
    postpone f+ postpone loop ; immediate

    : main3 ( -- )
    101 1 sum< 1e i s>f f/ >sum f. ;
    main3 cr

    Here SUM is split into two parts, and the TERM parameter is included
    as compiled code between these two parts, satisfying call-by-name's
    intent of textual replacement semantics.

    The parameter i in the Algol 60 variant is an artifact of Algol 60's
    way of working with variables; you also have to provide it to the for
    loop in Algol 60 and, e.g., Pascal. Forth produces the loop index of
    the inner counted loop with I, and so does SUM<...>SUM; an alternative
    would have been to provide the loop index on the data stack.

    The loop bounds are provided in the Forth-idiomatic form with the
    limit (hi+1) given first and not being included.

    The solution can be written slightly shorter using ]]...[[, but I
    decided to stick to Forth-2012 here.

    The Wikipedia page gives Sum(i, l, m, Sum(j, l, n, A[i,j])) as another
    usage example; this would become:

    : main4 ( -- r )
    m 1+ l sum< n 1+ l sum< A{{ j i }} f@ >sum >sum ;

    Here I used the FSL array syntax. One notable difference from the
    Algol code is that the outer loop index is J, while Algol uses the
    variable given (in this case i). That's the same as for other counted
    loops in Forth.

    - 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 2023: https://euro.theforth.net/2023
    --- Synchronet 3.20a-Linux NewsLink 1.114