An unstable isotope is one that cannot be stored in a variable. The clearest example of one of these is a PACK!.
Quick refresher: because functions receive their arguments in variables, you clearly cannot pass an unstable isotope to a function. You instead use ^META parameters and get the quasiform.
>> pack [3 4]
; first in pack of length 2
== 3
>> meta pack [3 4]
== ~['3 '4]~
>> [a b]: pack [3 4]
== 3
>> a
== 3
>> b
== 4
But what about non-variable situations... like expressions that are being tested for being truthy or falsey?
Right now, tests for truthiness throw the unstable state away. For instance, ALL:
>> all [1 2 pack [3 4]]
== 3
The PACK! formed by the PACK function is decayed to just 3 by the ALL, and then the 3 was tested as truthy. If you wanted to get past this, you would have to meta-and-then-unmeta the pack:
>> unmeta all [1 2 meta pack [3 4]]
; first in pack of length 2
== 3
But note that here, it's testing the meta-pack ~['3 '4]~ for truthiness, not the 3. So if false were in the meta pack, it would still wind up truthy.
>> all [1 2 meta pack [false false]]
== ~[~false~ ~false~]~
Whereas a regular pack would be decayed before the test:
>> all [1 2 pack [false false]]
== ~null~ ; isotope
You might ask "Why not just say all PACK!s are truthy, so you don't have to META and UNMETA them?"
Well just think about it: when you use a multi-returning function you may only be interested in the primary result...and even only be slightly aware that more results are available. For instance: not everyone knows that today's FIND returns an additional output if you want it (it's the end of a match).
Saying that packs must decay to test them for truthiness doesn't seem too baffling, but what about loop constructs that don't test for truthiness?
Is this harmless?
>> flag: true
>> meta while [flag] [flag: false, pack [3 4]]
== ~['3 '4]~
It seems all right, although this becomes a property that has to be preserved by compositions like FOR-BOTH. (As it so happens, the composition does work.)
But what about UNTIL? In until the loop condition and the body are the same, and in order to exit the loop the body must be truthy...so you can't get a pack out:
>> until [pack [3 4]]
== 3
And also... what about situations like CONTINUE/WITH or STOP/WITH. If the /WITH parameter is a plain refinement and not a ^META one, then the information will be lost:
>> cycle [stop/with pack [3 4]]
== 3
If CONTINUE and STOP were to follow in the footsteps of RETURN, they would need an added refinement like RETURN/FORWARD which distinguishes the case where you do want the meta parameter from where you don't (which is the current solution, vs. something like marking ^RETURN: as always being meta on the function definition).
Maybe Test DECAY'd Value, Preserve PACK! ?
Constructs like ALL or UNTIL might decay only for the purposes of the truthiness test, but preserve the original PACK! for the actual return.
For instance could you do this?
>> [a /b]: all [
1 = 1
2 = 2
pack [3 4]
]
== 3
>> a
== 3
>> b
== 4
I've pointed out that PACKs with falsey items have to break the chain:
>> [a /b]: all [
1 = 1
2 = 2
pack [false 4]
]
== ~null~ ; isotope
>> a
== ~null~ ; isotope
>> b
== ~null~ ; isotope
If you didn't intend that you'd have to META the pack inside the ALL...and UNMETA outside as discussed (in this case with UNMETA* which passes through null as-is vs. erroring if the leading conditions resulted in a null outcome)
>> [a /b]: unmeta* all [
1 = 1
2 = 2
meta pack [false 4]
]
== ~false~ ; isotope
>> a
== ~false~ ; isotope
>> b
== 4
Taking this preservation to extremes would complicate a lot of places in the code. For instance: IF tolerates function branches, and will pass on the condition to the branch:
>> var: [a b c]
>> if var (func [x] [print ["var was" mold x]])
var was [a b c]
But what if you want to do this with a pack, and have the option of receiving it in that function?
>> if pack [1 2] (func [x] [print ["var was" mold x]])
var was 1
>> if pack [1 2] (func [^x] [print ["meta var was" mold x]])
meta var was ~[1 2]~
>> if pack [false 2] (func [^x] [print ["meta var was" mold x]])
; void
Interesting though that may look, it changes IF to where it has to take its condition as a ^META parameter, otherwise it decays and can't be passed on.
I can't tell if this is a high-enough leverage piece of functionality to be worth complicating IF or not.
For Now, Tempting To Say "Loop Protocol" Can't Return PACK!
It's not necessarily hard to make /WITH a ^META-refinement on CONTINUE and STOP... at least in terms of allowing PACK!. continue/with raise error would cause some uncomfortable results.
Again, note there are ways to work around not processing unstable pack! isotopes by the /WITH itself, by META-ing on the inside and UNMETA-ing on the outside:
>> cycle [stop/with meta pack [3 4]]
== ~[3 4]~
>> [a b]: unmeta cycle [stop/with meta pack [3 4]]
== 3
>> a
== 3
>> b
== 4
Every time I see this, I do kind of wish up and down arrows were on our keyboards:
[a b]: ↓ cycle [stop/with ↑ pack [3 4]]
Caret-Meta only gets you half of this:
[a b]: unmeta cycle [stop/with ^ pack [3 4]]