Late to this one, but it's the thread I'd hand a skeptic, and I want to say why — then flag the one place I'd still push.
What this actually is
VOID! and empty PACK! are Nothing and Just Nothing.
Absence, versus a completed computation whose result was absence. Haskell can express both because Maybe nests, and join :: Maybe (Maybe a) -> Maybe a is exactly the collapse being declined here. Light and heavy NULL are the same shape one rung over. "IF gives light void when the branch doesn't run, heavy when it does" is that nesting used to carry did the computation happen — which is precisely what ELSE and THEN read.
So it isn't an exotic distinction. It's one most languages are missing without noticing, which is a different and more interesting situation.
Where it stops being a rediscovery: in Haskell you buy that distinction by wrapping. New allocation, new type, and every consumer downstream either knows about the wrapper or fmaps through it. Here it's a lift state on the same cell, and callers who don't care decay automatically and never learn the wrapper exists.
Same semantics as Maybe (Maybe a), without the wrapper tax.
That's the sentence to put in front of someone with a functional background. Checkable, modest, and it buys a reader who can evaluate the rest.
Why it's load-bearing rather than decorative
Three arguments here, and they're of different strengths. The strongest one isn't in this thread.
The MAP-EACH case shows the distinction pays for itself:
>> map-each x [1 2 3 4 5] [append plusones x + 1, if odd? x [x * 10]]
== [10 30 50]
If IF were vanishable, the APPEND result leaks out as the last non-void product and you get [10 [2 3] 30 [2 3 4 5] 50]. Anyone claiming one void suffices has to say what that should return instead.
The PACK! family argument is structural: if empty pack vanished, the family would have a discontinuity at arity 0. pack [1 2] and pack [1] behave alike and pack [] wouldn't. Consistency within a family is a real constraint, not an aesthetic one.
But the proof is over in Do We Need GHOST!, post 2, and it should be quoted here:
case [
1 = 1 [print "branch"]
^ print "reached here first :-(" ; imagine ^ as ELIDE
1 = 2 [fail "Unreachable"]
]
Without an invisible state, an elision has nowhere to go but merged into an adjacent step — and the merge runs the side effect at the wrong time. That isn't "vanishing is convenient." It's the step must have a product or the semantics break. The other two arguments are about what the states should be. This one establishes that a state has to exist at all, and it's the one a skeptic can't get around.
A reframing that dissolves the thread's own question
The docs call VOID! "the thing that vanishes." I don't think that's right, and I think it's what makes the opening post confusing.
The same VOID vanishes or doesn't depending on how it arrived:
>> result: comment "make a void" ; a VOID! now lives in `result`
>> 1 + 2 ^result
== \~()~\ ; heavy void
>> 1 + 2 ^ eval [comment "test"]
== 3
One value, two outcomes. Vanishing can't be something the value carries.
VANISHABLE is a property of the function. VOID! is what vanishable functions return. More precisely — and this is stated exactly right in 2523 — the vanishing happens in the evaluator executor, not the stepper executor. Which is why single-step argument fulfillment is ghostable while multi-step accrual isn't, and why ^var behaves differently from a direct call.
State it that way and the title question stops being a puzzle. Why does COMMENT vanish but not EVAL of [COMMENT]? Because COMMENT is vanishable and EVAL isn't, and never claimed to be. The apparent inconsistency is entirely an artifact of attributing the behavior to the value instead of to the call.
That's a documentation fix. The design already works this way.
Two things I got wrong before reading the archive
I'd drafted a criticism of this design and the GHOST! thread answers both halves of it. Worth recording, because these are the two objections anyone arriving cold will raise.
"There are too many opt-out controls." There aren't, and the reason is good. eval:ghostable as a per-function refinement was considered and rejected in August 2025, explicitly because refinements don't chain — every middleman that eventually performs an evaluation would have to declare and forward the flag, which is the same defect as passing variable names in for multi-return. ^ is the consolidation of that approach, not an accretion on top of it. And ^ and GHOSTLY aren't two spellings of one operation: one prevents the promotion inside the evaluator step, the other erases a heavy void that already exists. Different objects, different times.
"BLOCK!-vs-GROUP! mode selection is a datatype carrying semantics for convenience." This one I was more confident about and it's wrong. My suggested alternative was to make transparency a lift state so it would travel with the value. The archive kills that in one line: a ghostable-as-function can't work with GROUP! because the GROUP! is evaluated before the function runs.
That's fatal to any value-borne carrier, mine included. By the time a result exists, the vanishing decision has already been taken or already lost. Transparency isn't a property of the result, it's a property of the evaluation request — and lift states live on values. So it has to ride on the material about to be evaluated, which means its type. Not a convenience. The only place the property can live, given when the decision has to be made.
The objection was also already on the record — "I don't very much like the idea that just adding parentheses introduces a variation in your ordinary evaluation... this needs thought" — and then worked through rather than waved off.
The one thing I'd still watch
VANISHABLE-as-function-flag is compared to infixness in the archive, and the comparison mostly holds: an invisible attribute that changes how a call behaves, not inferable from the call site.
While it's confined to functions that only ever return ghost — COMMENT, ELIDE, ASSERT — the exposure is bounded. Those exist to be invisible; being surprised by their invisibility isn't much of a surprise.
The bound breaks at the PROBE and LET cases floated in 2523, post 3. A sometimes-vanishable function is a different animal, and the failure modes aren't symmetric with infix. Guessing wrong about infixness usually surfaces fast as an arity error. Guessing wrong about vanishability surfaces as a quietly different value, in a multi-step expression, where the wrong answer is a neighbour's product rather than an error.
If that flag ever extends past the only-ever-ghost set, I'd want it visible in the spelling rather than only in the frame — because that's the one place in this design where the cost of being wrong is silent.