Wrapping And Forwarding Multi-Returns

Yup, sounds really useful.

One question, what do functions see?

f: function [a][]
f multi-return

Does f get the return as a block, or the first value?

What happens with multiple parameters in f? With refinements? With variadics? When f is enfixed?

That was six questions. :slight_smile:

The current implementation has it so that f would only receives the first value (main return) of the multi-return.

But that raises some pretty big questions about how you'd deal with situations where that's not sufficient.

A Huge Question: Forwarding

The premise this is operating on is that multiple return-valued functions are a fundamentally different beast. Any single value goes through assignment normally to the first slot:

>> [x y]: [1 2]
== [1 2]

>> x
== [1 2]

>> y
== ~  ; isotope

This magical property of functions has a bit of a problem, though...

my-wrapper: func [arg1 arg2] [
     return multi-return arg1 arg2

If MULTI-RETURN has the property that it would assign multiply to a SET-BLOCK! target, then you'd lose that multiple-return-ness. You can't have it both ways...a value like a BLOCK! can represent a single multi-valued item, or multiple values, but not both at the same time.

It seems clear there'd need to be some kind of operator for unpacking and repacking a multi-valued function's results into a BLOCK!. Since UNPACK is taken, let's call them BUNDLE and UNBUNDLE:

 >> one: multi-returns-10-and-20
 == 10  ; drops 20, not requested

 >> both: bundle multi-returns-10-and-20
 == [10 20]  ; assume request is for *all*?

 >> [x y]: unbundle [10 20]
 == 10

 >> x
 == 10

 >> y
 == 20

That doesn't help with the wrapping problem, though. You'd need some kind of forwarding return. Perhaps something like a return/bundle?

It might seem nice to make forwarding the default, but this would lead to counter-intuitive situations, like these two being different:

 return some-function

 x: some-function
 return x

It feels like you'd want some kind of signal before having these act differently.


Back To Basics: What About (almost) NO SET-BLOCK! Behavior?

I realized that what I was trying to do was to split the behavior of multiple returns across two parts: the SET-BLOCK! on the left, and the function dispatcher mechanics on the right.

However you slice that, it means the SET-BLOCK! winds up asking for something from its right-hand side besides "give me the single result of your evaluation". That requires baking some kind of protocol in at a pretty deep level.

But what if SET-BLOCK! was "dumb" by default. All it would do would be to set the first item to the evaluation of the right hand side, and unset the rest:

>> [x y]: 1020
== 1020

>> x
== 1020

>> unset? 'y
== #[true]

Then we could basically say that all multi-return functions are actually enfix, and take a SET-BLOCK! on the left hand side that's <skip>-able.

They'd have a little prelude which would evaluate any groups in the block on the left hand side first (so things are coherent left=>right). Then they'd run their body, and have an epilogue which did the writeback to the variables.

But Isn't That How Multi-Return Was Protyped?

Well, yes. And it's how UNPACK works.

I never said making all FUNCs that were multi-return implement themselves as enfix was a bad idea.

In fact, given UNPACK as an example...I think it shows that a sort of "sky's the limit" definition of how your function chooses to interact with a SET-BLOCK! is really more interesting than trying to come up with some standard definition of what a "multi-return" is.

[<special-instruction> a *]: your-special-function ...

We can have some basic expectations (and if you're going to use an unmodified LET you'll have to accept a few of those, though it passes through TAG!s and such).

Equally Broken with GROUP!s, but Answers for APPLY etc.

Trying to formalize multi-return beyond enfix wasn't really leading to anything better.

For instance, allowing GROUP!s might sound nice, which enfix definitely can't do:

[value rest]: transcode data

[value rest]: (transcode data)

But once you're "inside the system" with the power to do more, do you really want to? What about:

[value rest]: (transcode data, 1 + 2)

[value rest]: ((^(transcode data)))

Trying to pick which of these situations you're going to rig a protocol around is dicey. Also, how would you solve something like:

[value rest]: apply :transcode [data]

The enfix actually gives us an answer. APPLY can just notice that :transcode is interested in things on its left hand side, and give it the SET-BLOCK!...proxied as a parameter. The same thing will work for UNPACK, even with things like its weird ellipsis semantics for "ignore additional values":

>> [a b ...]: apply :unpack [reverse [<dropme> 304 1020]]
== 1020

>> a
== 1020

>> b
== 304

So being dumber is actually smarter, in this particular case.