Multiply-Unpacking Multi-Returns

I haven't really figured out my philosophy about putting multi-returns in multi-returns.

Looking at the question just in terms of fundamentals...should this be legal?

>> x: pack [pack [1 2] 3]
== 1  ; double-decay?

And this seems rather ambiguous:

>> [x y]: pack [pack [1 2] 3]
== ???

Is that x = 1 and y = 3? Or x = 1 and y = 2 ?

I'll point out the availability of nested block notation:

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

>> x
== 1

>> y
== 3

So it seems to me that should be legal, in the sense that you've indicated that you know what you're doing in the unpacking. It isn't necessary for the first element to be a pack...

>> [[x] y]: pack [1 3]
== 1

>> x
== 1

>> y
== 3

You're just covering the case of demonstrating that you know the first thing might be a pack.

Case Study: EVALUATE

I've talked about how using :STEP changes the return result of EVALUATE into a multi-return unconditionally, with the synthesized product as the second result.

This is to say that if you have a function that returns multiple values, you can dodge ambiguity by making it so that the first result can never be a multi-return. This lets you unambiguously interpret the multi-return.

So in places where EVAL:STEP is used, I've effectively been saying this is legal:

>> [pos result]: pack [[print "hi"] pack [2 3]]
== [print "hi"]

>> pos
== [print "hi"]

>> result
== 2

If you wanted to unpack it further, you'd need [pos [a b]]: or to ask for the pack in its meta form, such as by [pos ^result]:

If this is allowed, it makes it seem "unfair" that the first position wouldn't double-decay. Would it be too much to ask to require you to say [pos [result]]: if there was a chance of the second value being a multi-return?

That feels like too much to ask to me. If I could write result: some-multi-return-func then it seems I should be able to write [pos result]: eval:step [some-multi-return-func]

But if double decay is allowed, does that mean triple-decay is too? Arbitrary-recursive decay where you get an infinite loop, by building self-referential packs?

Is it better to have a rule that the first slot won't double-decay, or just enforce it by convention by trying not to design functions that are multi-return which have multi-returns in their first position?

Another Case Study: PARSE

Right now, PARSE is broken into layers... a lower level PARSE* that is always a multi-return function (if it's not raising an error), and it gives back a synthesized result as well as an array of residual "pending" items.

(Residual pending items are leftover items that combinators produced in the pending list, that were neither rolled back nor consumed by another combinator. It's what you'd get if you did something like parse "a" [keep "a"], e.g. did a KEEP with no corresponding COLLECT to filter the collected item out of the list. Sometimes residual pending items are intentional, e.g. in the Visual Parse Demo where a list of underlines--produced by any MARK combinators that don't get rolled back--bubble up to the EPARSE driver so it can draw those underlines.)

The higher level PARSE doesn't offer the pending items--it just errors if it sees any leftovers. The problem would be that without these layers, the default interpretation of unpacking wouldn't be able to unpack multi-returns synthesized by parse:

>> [x y]: parse "bbb" [some "a" (pack [1 2]) | some "b" (pack [3 4])]
; I want to get x = 3, y = 4 from this

The desire to have this be the common behavior is why PARSE* is needed to get the pending list as a multi-return.

But I've faced some problems with wrappers over the lower level PARSE* having to redo work, e.g. to implement the PARSE:MATCH refinement, which can't be put on the lower level parse and needs to be repeated in each wrapper.

It might be that thinking of this in terms of "higher level" and "lower level" is wrong, and I should follow the method of EVAL to actually use a refinement to change the return result of a single PARSE construct. So PARSE:PENDING or somesuch, to shift into a multi-return mode where the pending list is the main result, and the synthesized result (which may be a multi-return) is secondary.

Anyway, I'll take another look at this. Questions remain about how many decays to allow, but so long as this is seen as a kind of video game we shouldn't be afraid of infinite loops. They're trivial enough to cause in other places. :man_shrugging: Just make sure you can Ctrl-C out of it and try to give some guidance... (hopefully you can debug step through the decays, I'll have to think about that).