The concept of an "undecayable PACK!" came up when I was trying to reason about what should happen when you had things like PACK!s of PACK!s...
>> x: pack [1 2]
== \~('1 '2)~\ ; antiform (pack!)
>> x
== 1
>> x: pack [pack [1 2] 3]
== ???
My belief was that this should panic, rather than do the decay. If you truly wanted the first element of an unpacked pack to decay, you could write:
>> [x]: pack [pack [1 2] 3]
== \~(~[1 2]~ '3)~\ ; antiform (pack!)
>> x
== 1
This created the notion of an undecayable pack. The first cut of the undecayable rule was that that any pack with an unstable antiform in the first spot (VOID!, PACK!, ERROR!) would panic if you tried to decay it (vs unpack it).
Safety Motivation: Don't Let ERROR! Disappear
Putting ERROR!s in PACK!s isn't the usual way of doing things. If a function runs and wants to make you aware of an ERROR!, it should typically return that as the main result of a function...not stow it away inside a pack. Only if it's the main result can it be reacted to by things like EXCEPT.
But some cases don't allow the error to be the sole return result. For instance, EVALUATE:STEP
[pos ^result]: evaluate:step [fail "abc" ...]
We need the updated position of the code, even if the code FAIL'd. Hence, EVALUATE can't make its main result an ERROR! in the case of an error result, it has to give back a PACK! which encodes both the error and the new position.
("Has to" is a strong statement... it's possible that the next position could be a field of the error, I've discussed some of these odd ideas before. But it's better to allow errors in packs.)
If you don't receive result as ^META, then the ERROR! wouldn't be able to store in the ^result and would panic:
[code result]: evaluate:step [fail "abc" ...] ; not ^result, so panic
But what if you didn't store the result at all?
code: evaluate:step [fail "abc" ...] ; ???
I feel like that should panic. But why should you have to do an assignment to get the panic?
evaluate:step [fail "abc" ...] ; same behavior as when assigned to `code:` ?
That would suggest that if a PACK has an ERROR! in any slot, then should the pack decay and not extract that error into a ^META variable, that error becomes a panic.
But this thought leads to another invasive thought...
...What About ERROR!s In PACK!s... in PACK!s?
I started by talking about undecayability, e.g. you can't put a PACK! in the first position of a PACK! and have it "double decay". Decaying happens once.
And then I discussed decaying when there's an ERROR! in the pack... suggesting that an error at any position (not just the first) which is not unpacked into a ^META-variable should panic.
But what if you have a PACK! that's not in the first position of a PACK!... ? Should that be willing to decay silently?
>> x: pack [1 pack [2 3]]
== 1 ; silently discarded PACK! in second position...
That may seem harmless, BUT, what if that PACK! contained an ERROR! ?
This led me to theorize that packs which contained unstable antiforms at any position would be "undecayable"
However, that started to feel too strict, when we look at:
[code result]: evaluate:step [pack [1 2] ...] ; works, result decays to 1
code: evaluate:step [pack [1 2] ...] ; innocuous, why shouldn't it work?
And even if I'm assigning the result, PACK!s propagate in assignments now by default
while [[code result]: evaluate:step code] [
...
]
It would be a shame if this decayed a PACK! for result, and then refused to decay it for the WHILE, making you write:
while [[{code} result]: evaluate:step code] [
...
]
So pre-emptively refusing to toss PACK!s just because they might contain an ERROR! doesn't seem very ergonomic.
Non-First Item Recursive PACK! Decay Search For ERROR!
So... what if any PACK!s which aren't in the first position, that you want to discard, will recursively unpack themselves looking for ERROR!, and if they find any then panic?
(And ERROR!s which aren't in the first position, and aren't in PACK!, are also sources of panic?)
This would make the system more robust to dropping errors on the floor. This might make it more reasonable to say that PACK is willing to pack up errors, generally... today you have to use a special operation:
pack [1020 fail "won't work"] ; panics
pack* [1020 fail "will work"] ; allows the error
It's a little bit disconcerting, to imagine that instead of erroring at the moment of the PACK you trust that wherever the pack is going will handle the error. But really, that only becomes a problem when you're dealing with using PACKs in non-multi-return situations, e.g. you make a PACK and then put it in suspended animation somewhere.
If you're not putting packs in suspended animation, but "packing with the intent of unpacking", then you're leaving it up to the recipient as to whether they want the error or not.
All things being equal, having just one PACK primitive is preferable.
Things do seem to be falling into place in other ways, and I'm sensing that maybe the right thing to do is to say that if you're a client with "pack with intent to store", then you bear the burden of the indefinite lifetime you may give errors in that pack...vs. making it harder to put errors in packs.
![]()