Variadic Return Values (not VARARGS! return values...)


#1

Imagine the following:

inline: function [
    return: [any-value! <variadic>]
    block [block!]
][
    return make varargs! block
]

With the desired behavior:

>> 1 inline [+ 2 + 3]
== 6

This would be a very powerful macro-like mechanism. (While not being done with any preprocessing step, and hence not a “macro” in the conventional sense).

One of the mechanical problems with such a construct is that when the evaluator is looking right from the 1, it has to know if the function it is seeing is enfix or not. Unfortunately, INLINE doesn’t have a generic answer…its answer depends on calculations it does once it is running, after its arguments are evaluated. It depends on what’s in the block it gets.

The evolving way of dealing with such situations is left-variadic-enfix. This is to say that inline can defer its answer until when it runs…but by doing so it runs before its left hand side.

Here is simplified model of how that would work:

inline: enfix function [
    return: [any-value! <variadic>]
    :look [<opt> any-value! <variadic>]
    block [block!]
][
    if enfix? w: match [word!] first block [
        return make varargs! compose/only [(take look) block]
    ]
    return make varargs! block
]

The moment of the TAKE (or the lack of a take) is how the evaluator gets its “are you enfix or not” answer from something whose left hand side is variadic.

While this looks very similar to what SHOVE does, SHOVE is actually implemented as an internal trick to the evaluator that is tailored to that particular case. It doesn’t run usermode code (or even plain code inside a native) to accomplish what it does. It can’t, because the variadic take of the left would require completing a higher stack level of code before a lower stack level.

It may seem variadics do this already (there’s an ACTION! on the stack whose frame is running, and code is being processed from its “feed” to supply values to a variadic further on in the stack). But that action is already running and has finished gathering its arguments–you’re not interrupting the evaluator in mid-process, and hooking into the enfix mechanic itself. I think it’s possible though–it might need to use the same trick as ENCLOSE, which rearranges the stack in mid-run.

It would still be a little odd–in the same way that SHOVE is odd. One odd thing is that the evaluation of the block on the right has to happen before any evaluations on the left:

>> (print "left" 1) inline (print "right" [+ 2 + 3])
right
left
== 6

That’s just the physics of the situation. The left looking rightward needs the answer to a question that can’t be answered until the right is evaluated. But it’s not just SHOVE that has this property, look at SET-PATH! operation… right before left:

>> o: make object! [x: 10]
>> o/(print "left" x): (print "right" 20)
right
left
== 20

The reason for why it has to do that is similar. I’ve made a semi-convincing argument for why this is not a bug, and if there are other constructs which have to do this then it seems less like an anomaly.

Reading between-the-lines, this would provide a highly generic tool for rewriting the stream of code…you could imagine things that take a VARARGS! in, and give a VARARGS! out.

Avoiding Superfluous Stack Levels

One area I’d like to see this applied is when you want to avoid introducing a stack level in the debugger. e.g. the console has to run a block of code, but you don’t want to see “DO BLOCK” putting DO on the stack. Instead the API could just say:

result = rebRun("inline", block);

This way, when the code inside the block is running, INLINE has already finished its work…handing the feed back to the evaluator. It will appear there’s nothing at all on the stack.

Because of the trickery involved, I imagine this would end up being slower than DO BLOCK (I’d basically be certain of it), so you wouldn’t want to do it unless there was a good reason. But there may be some cases where code is getting composed or wired up today to accomplish this where it would be faster. Since it’s a hypothetical feature at this point, it’s probably best to wait to speculate too much on this.


#2

CHAIN got some new fun functionality recently, where functions of arity > 1 can be used as chain steps…they just pick up their arguments in order normally:

>> addmul: chain [:add | :multiply]

>> addmul 1 3 5
== 20

@IngoHohmann thus asked what this would mean for functions which returned variadics. Remembering that these do not exist yet (and will be challenging to write), this is only a thought experiment…but I imagined:

>> four-and-more: chain [specialize 'inline [block: [4]] | :multiply]

>> four-and-more 5
== 20

>> two-and-more: chain [specialize 'inline [block: [+ 2]] | :multiply]

>> 1 two-and-more 5
== 20