Multi-Return: Deferred Enfix + Meta Returns

So multi-return is based on a (conceptually) simple trick for writing ordinary function calls. The "extra returns" are really just refinements that are labeled as outputs, but you can invoke them normally as refinements that take a WORD!... as was done historically for extra outputs of a function.

Traditional code:

>> block: transcode "1 2 3"
== [1 2 3]  ; defaults to assuming you want the whole thing transcoded

>> value: transcode/next "1 2 3" 'rest
== 1  ; asking for /next gives you just one value

>> rest
== " 2 3"  ; the REST word passed in gets assigned the remainder

Now for some :candy: Syntactic Sugar :lollipop: that is enabled when NEXT is marked as an "output refinement" (next:) instead of an "ordinary refinement" (/next) in the function spec:

>> [value rest]: transcode "1 2 3" 
== 1

>> value
== 1  ; the overall expression was 1, but value was also assigned 1

>> rest
== " 2 3"  ; just as if you'd named the /NEXT refinement and passed 'REST

But There Have Been Enfix Complications...

Though simple in concept, the multi-returns aren't completely simple in implementation...and at present run through a bit of different code than ordinary function calls (though of course most of the function execution, type checking, etc. are shared).

The parts that are different--however--were creating problems with enfix...which wasn't managed as part of the frame filling process. So it just errored.

>> [value rest]: transcode "1 2 3" then [<item!>]
** Script Error: Ambiguous infix expression--use GROUP! to clarify

You could get it to execute by putting the part before the THEN in a group:

>> ([value rest]: transcode "1 2 3") then [<item!>]
== <item!>

Which works...but what if that isn't the semantics you wanted? That means your overall expression evaluates to <item!> but value is still getting 1.

Consider that we usually want the result of an ELSE to get into the value of a variable:

>> x: if 1 > 2 [10] else [20]
== 20

>> x
== 20

If you had to write this with a GROUP! you'd get x as the result of the IF (null in this case, since 1 < 2)

>> (x: if 1 > 2 [10]) else [20]
== 20

>> x
; null

So that's the situation we were in with multi-returns--not having a choice. You'd wind up with the variable assigned the result of the original function...never being able to use the enfix product.

It looks like I have a fix for this, so you'll be able to choose either way.

Recap of The No-Group-On-Right Limitation

I've mentioned how being syntactic sugar kind of limits what we can do with multi-returns. You can't put the right hand side in a group:

[value rest]: (transcode "1 2 3")

This is because it could be more than one call, and you don't know what will come after it, and it could be arbitrarily deep:

[value rest]: ((((transcode "1 2 3", ...))))

You might argue we could still allow it if the multi-returns are tunneled down to the function and the value gets assigned from the final product of the group.

But I don't like it because then you are talking about something that left quoting enfix couldn't do. I like the idea that when you get in a pinch and want to override a multi-return assignment you could do it by declaring your function enfix and pick up the SET-BLOCK! on the left as a parameter.

That interchangeability offers a good dynamic, and it's how things like UNPACK fit into this universe...where they seem like first-class language features. Doing crazy things that don't relate to what users could build themselves isn't the ergonomic we're looking for (e.g. "bad lego alligator" territory).

The Meta Dilemma

The hard rule that the thing on the right of the SET-BLOCK! be the function with the multi-returns caused a problem with wanting to ask for the "meta" result.

UPARSE has this desire often. It may be that a parser returns something like an unset or a null isotope. We want to handle those distinctly from nulls, as well as to not choke on them as invalid variables. So we ask for the meta-result of the parser.

I wrote a special exemption to allow it, like this:

([result progress]: ^ parser input) then [...]

Recognizing the ^ specially in the multi-return code felt wrong. Where do such things stop...why not ([...]: ^ ^ ^ parser input) ? It has a similar arbitrariness to it that digging through parentheses seemed to have.

But with the META-WORD! we had a new option...to put the meta on the argument itself:

([^result progress]: parser input) then [...]

To some people's tastes maybe that looks worse. but it is more compact. And it can work even if you don't name a variable:

>> [^ rest]: transcode "1 2 3"
== '1

It feels more like things are in the right place this way. Multi-return was prototyped as just an application of enfix quoting blocks on the left of a function...and I kind of like it not straying too far from what that could do. This is within reach of that.

These might seem like small things, but, they are important.

3 Likes