Variadics vs. Trampolines (or VARARGS! vs. RETURN/REDO)


On Red’s, @UnchartedWorks wrote:

Red isn’t functional, it can’t return a function and take arguments.

Historically R3-Alpha had something called RETURN/REDO:

It was disliked because it had a kind of invisible “contract violation” on the interface of the called function. You’d type HELP on something and it would look like it was arity-1, but then act as arity-10 sometimes and arity-5 sometimes. APPLY would no longer have a consistent contract. Most of the actual interesting applications were just very convoluted ways of writing variadics, and many Rebolers were opposed to variadics on principle.

The feature was removed, though it remained as an implementation detail of DO in R3-Alpha. When DO was passed a FUNCTION!, it would “bounce” it and run it, using the same mechanism that RETURN/REDO had been based on:

>> do :add 1 2
== 3

But even there it shows it is problematic, because now DO has a magic power no other function has…and wrapping it becomes difficult. Let us say that when DO takes a URL! you wish to give an error, but act the same otherwise.

>> safe-do: func [value] [
     if url? :value [fail "DO of a URL! is disabled"]
     do :value

If you try safe-do :add 1 2 you will not get the behavior of DO wrapped as you might have liked, because it will attempt to collect the parameters at the do :value site, and see the end of the block.

Ren-C has addressed this problem by introducing variadics as a formal interface concept. DO is not variadic, and is hence constrained to taking one argument (or additional arguments as appropriate for refinements). But a function called EVAL is variadic, yet lacks any interesting behavior beyond evaluator triggering:

>> eval :add 1 2
== 3

>> eval "hello"
== "hello"

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

By isolating this functionality into a routine that has no other complex behavior (the way DO has) the fact that one has to do a bit of “extra work” to wrap it doesn’t come up so often.

In any case, with variadics it is possible to be as sketchy as you like in Ren-C w.r.t. function retriggering, it just has to be part of your contract. Rather than returning a function and asking the system to /REDO it, it’s just that if a function is variadic, it has an opportunity before it returns to consume additional parameters from the callsite, and pass them to a function it calls directly.

add-or-reverse: func [args [string! integer! <...>]] [
    a: take args
    either string? a [
        reverse copy a
        b: take args
        add a b

>> add-or-reverse 1 2
== 3

>> add-or-reverse "hello"
== "olleh"

Note that if one wishes to specialize a variadic function, one can.

 >> f: specialize :add-or-reverse [
     args: make varargs! [1 2]

>> f
== 3

>> f: specialize :add-or-reverse [
    args: make varargs! ["hello"]

>> f
== "olleh"

If you want a variadic to be able to do anything, it needs to be hard quoted and accept ANY-VALUE!. Then you can, for instance, DO it.

mr-do: func [:feed [any-value! <...>]] [
    print "About to DO"
    result: do :feed
    print "DO is finished."

>> mr-do print 1 + 2 print 3 + 4 "hello"
About to DO
DO is finished
== "hello"

There is a lot of flexibility in the approach, and composability isn’t hurt as much as one would think. It actually allows one to do some very interesting evaluator hooks. While it was once a fringe feature, it has been solidifying over time…here’s some old documentation:


It confused me. Thanks you so much! I have a better understanding of it.