Re-imagining DO/NEXT (now called EVALUATE)

UPDATE: Because it's confusing for DO to change the result it gives based on a refinement, the /NEXT refinement has been removed from DO...and the following behavior is given by a routine called EVALUATE. So the DO/NEXT suggestion below is now implemented as EVALUATE/SET.

What if DO/NEXT returned the position, evaluated result into var?

e.g. like this:

>> block: [1 + 2 10 + 20]

>> block: try do/next block 'var
== [10 + 20]

>> var
== 3

>> block: try do/next block 'var
== []

>> var
== 30

>> block: try do/next block 'var
== _

>> var
== 30

This has a differentiated step for the end. And if you try to execute a DO/NEXT on the end of a block, you get null back... but it doesn't overwrite the var if you were at the end.

This offers a pleasing aspect to how it might be used in a while loop. You can style the loop to just call DO/NEXT, without checking anything for the TAIL...

while [block: try do/next block 'var] [
    ...
]

And in addition, you have access to the last evaluated value after the loop. Nice, isn't it?

That's using TRY, but since it obeys the null protocol, you're free to do other things...e.g. this code for finding the first non-null evaluated element in an array (something that might be used by a REJOIN-type operation)

until [
    block: (do/next block 'base) else [return null]
    set? 'base
]

Not just "nice", a solution for a nuanced problem (coincidence?)

ELIDE is great, but it has had an unfortunate problem of running as part of the evaluation that precedes it.
The reasoning for the behavior was summarized here. Basically, if you are evaluating a block with a chain of DO/NEXT steps you don't want to get to your last evaluation and realize you've only got invisibles left... when you wanted to evaluate to the last actual result--now thrown away.

But wait... this could be applied to solving that. Because of that last step in the DO/NEXT which returns the null but doesn't overwrite the variable. If there happened to be some residual invisible data vs. just a tail, it works the same way. For instance:

>> block: [elide (print "a") 1 + 2 elide (print "b") 10 + 20 elide (print "c")]

>> block: try do/next block 'var
a
== [elide (print "b") 10 + 20 elide (print "c")]

>> var
== 3

>> block: try do/next block 'var
b
== [elide (print "c")]

>> var
== 30

>> block: try do/next block 'var
c
== _

>> var
== 30

Ta-da. For ELIDE's "lazy" invisible category, this can be made to work. It's pretty nifty that it doesn't interrupt the evaluation stream with anything...it can have side effects, yet the evaluations act like it's not there at all.

This is easier said than done, but I've got a preliminary experiment working--suggesting that I see no reason it couldn't be made to work.

2 Likes