I have a general comment about this thread.  It seems to me to point
up the difficulty of writing first-class control structures in REBOL.
I haven't seen in this thread (nor have I been able to think of) an
approach that actually behaves as well as a native control structure.

To illustrate this, let me take two of the best examples from the
thread and show how to break (or, at least, expose) them.

[EMAIL PROTECTED] wrote:
> 
> pif: func [[throw]
>     {polymorphic if with lazy evaluation and minimal checking}
>     args [block!] /local cond blk r result
> ] [
>     result: false
>     while [not empty? args] [
>         either cond: first r: do/next args [
>             either logic? cond [
>                 r: do/next second r
>                 if not unset? blk: first r [
>                     result: do blk
>                 ]
>             ] [
>                 result: do cond
>             ]
>             args: tail args
>         ] [
>             r: do/next second r
>             args: second r
>         ]
>     ]
>     result
> ]

When I use this proposal as follows

    testcase2: func [/local a b] [
        for a 1 3 1 [
            for b 1 3 1 [
                pif [
                    a < b [print [a "<" b]]
                    a = b [print [a "=" b]]
                    a > b [print [a ">" b]]
                ]
            ]
        ]
    ]

I get the following:

    >> testcase2
    1 = 1
    ** Script Error: result needs a value.
    ** Where: result: do blk

due to the fact that  print  doesn't return a result:

    >> foo: print 123
    123
    ** Script Error: foo needs a value.
    ** Where: foo: print 123

Therefore, caching the result via  result: do blk  can break if
there's no result from  do blk .

I did think about wrapping all of that in a try block, and then
playing tricky flag games at the end of the function based on
whether  result  ever got a value, but that seemed unbearably
crufty.

It also didn't solve the whole problem, AFAICT, due to the same
issue that occurred in this proposal:

[EMAIL PROTECTED] wrote:
> 
>     cases-dialect: make object! [
>         "Dialect for do-cases"
>         else-if: if: func [
>             condition
>             body [block!]
>         ] [
>             system/words/if condition [
>                 do body
>                 true
>             ]
>         ]
>         else: :do
>     ]
> 
>     do-cases: func [
>         "Executes the case whose condition is true"
>     ;   example for cases:
>     ;       if cond1 [code1] else-if cond2 [code2] ... else [default]
>         cases [block!]
>     ] [
>         any bind cases in cases-dialect 'self
>     ]
> 

Again, a nice piece of code, but look what happens with

    paranoid1: func [a b] [
        do-cases [
            a <= 0 [return 0]
            b <= 0 [return 0]
            else   [print "OK"]
        ]
        print "Computing now!"
    ]

which is obviously a stripped-down skeleton of a function that
wants to be sure it was given safe arguments.

    >> paranoid1 4 6
    Computing now!
    >> paranoid1 -4 6
    Computing now!
    >> paranoid1 4 -6
    Computing now!
    >> paranoid1 -4 -6
    Computing now!

The problem, AFAICT, is that the  [return 0]  is being evaluated in
do-cases, which returns a zero result to paranoid1 (as the value of
the do-cases evaluation), which then happily falls through to
the evaluation of the rest of its body.

SO...

To build a truly first-class control structure, we must pass to
it one or more blocks which will be conditionally/repetitively
evaluated WITHIN THE CONTROL STRUCTURE ITSELF.  How can we make
the "natural" behavior occur within the caller for all of

    controlled block that evaluates to a value
    controlled block that evaluates with no value (just a
        side effect, such as 'print)
    controlled block that evaluates 'return (producing
        a return from the CALLER)

(are there any others?)

The closest I've been able to come to addressing these behavioral
issues is

    first-true: func [b [block!]] [
        foreach [c r] b [
            if do c [return r]
        ]
        return []
    ]

which would have to be used as follows:

    testcase3: func [/local a b] [
        for a 1 3 1 [
            for b 1 3 1 [
                do first-true [
                    (a < b) [print [a "<" b]]
                    (a = b) [print [a "=" b]]
                    (a > b) [print [a ">" b]]
                ]
            ]
        ]
    ]

    paranoid3: func [a b] [
        do first-true [
            (a <= 0) [return 0]
            (b <= 0) [return 0]
            (true  ) [print "OK"]
        ]
        print "Computing now!"
    ]

with results of

    >> paranoid3 4 6
    OK
    Computing now!
    >> paranoid3 -4 6
    == 0
    >> paranoid3 4 -6
    == 0
    >> paranoid3 -4 -6
    == 0
    >> testcase3
    1 = 1
    1 < 2
    1 < 3
    2 > 1
    2 = 2
    2 < 3
    3 > 1
    3 > 2
    3 = 3

Well...  This addresses the behavioral problems, but at a TERRIBLE
cost in appearance!  Having to have the explicit 'do in the caller
certainly breaks any illusion that this is a "real" control
structure.  (I am assuming that a  do/next  approach would take
care of the need for parens on the condition.  I just wanted to get
the  'do  issue out for discussion with minimal coding.)

Any more thoughts, anyone?

-jn-

Reply via email to