Meet the REEVALUATE (REEVAL) native

Part 1: The menace that was variadic DO

Rebol historically avoided variadic functions. People might have wanted to say something like do [summation 1 2 3 4], but summation [1 2 3 4] was considered a better interface.

While there was no end-user feature implementing variadic functions, there was one loophole. DO looked like it was arity-1, but it could effectively act with an arity of (1 + the arity of a function passed to it)

rebol2> do :negate 1 ;; 2 arguments to DO
== -1

rebol2> do :add 10 20 ;; 3 arguments to DO
== 30

It's not obvious to everyone that DO is acting variadically; they might say "it's not DO, it's the function". But how did that function get executed? Who is running it? If you tried to write your own MY-DO, would you have been able to?

The answer is no, and you can see this if you tried writing something like the following wrapper (leaving off refinement processing like /NEXT, which is particularly annoying in old-school Rebol, prior to ADAPT and CHAIN and SPECIALIZE and such).

old-do: :do
do: func [value [any-value!]] [
    if value = %dangerous-file.reb [
        fail "dangerous-file is not allowed to be run in this Rebol"
    ]
    old-do :value
]

Well, what's wrong with this wrapper? If you try calling the new DO with a FUNCTION!, the wrapping won't work as expected, because when it gets to old-do :value and that value turns out to be something like the ADD function, it will try and gather the arguments from the callsite of OLD-DO. Which is the wrong place to be gathering them.

The perceived "un-wrappability" of variadics is one of the reasons that they were deemed a menace, and rejected from the language. In fact, it seems Red hasn't made DO variadic at this time (you can ask them if the plan is never to, which would be my suggestion).

red> do :add 10 20
== 20

Doing a FUNCTION! in Red seemingly just returns it, so DO :ADD was discarded and you got the 20 as the last result:

red> do :add
== make action! [[
    "Returns the sum of the two values" 
     value1...

Part 2: Enter REEVAL

My impression was that the biggest problem with DO performing variadic evaluation was that the DO function had rich behavior and was used for other things. e.g. running scripts from a filename, you might want to wrap it. If you tried your wrapper could never keep the behavior when a FUNCTION! was passed.

But what if there was a function whose sole purpose was variadic evaluation of inline values? It would be "impossible" to wrap, but who would want to? It only has one job. Hence REEVAL was born.

(Note: REEVALUATE/REEVAL were initially called EVAL. However that has been taken for a more fundamental task, which is the replacement for DO/NEXT...where a new interface was needed.)

>> reeval :add 1 2
== 3

>> x: 10
>> reeval (quote x:) 20
>> print x
20

>> reeval [some block]
== [some block]

>> some-group: quote (1 + 2) 
>> reeval some-group
== 3

We see that from the behavior with not doing anything to a BLOCK! that it's a different operation from DO. It's something that gets a value and then continues inline processing as if that value was the start of a single do step. So you might approximate eval x y z... as something akin to:

 do/next compose/only [(x) y z...]

So REEVAL is a variadic function which exposes the functionality of a single evaluator step. Because Ren-C allows you to define variadic functions, it would be theoretically possible to write such a function yourself in Rebol code. In practice, doing so would mean re-implementing a bunch of the Rebol evaluator in Rebol, which would be a pain to try.

5 Likes