Should GET-WORD! of a VOID! raise an error?

The following is the behavior of Rebol2 when a GET-WORD! is used on an "UNSET!" value:

rebol2>> unset 'x
rebol2>> type? :x
** Script Error: x has no value

So you would have to fall back on GET/ANY to really get a VOID!

rebol2>> type? get/any 'x
== unset!

But R3-Alpha decided to be more lenient in this respect, and Red followed the lead:

r3-alpha>> unset 'x
r3-alpha>> type? :x
== unset!

red>> unset 'x
red>> type? :x
== unset!

I've always been skeptical of this, because quite often the reason you are using a GET-WORD! is because you think the thing in your hand might be a function and you don't want to call it. But when you let it mean "get a thing that's not set" as well, you're opening the doors to letting typos through:

return reduce ["My Action" :my-actionn]  ; whoops, I meant MY-ACTION!

Is it worth the tradeoff? If anyone would say yes, it would probably be me... because I argue for the importance of being able to write truly generic code... so that usermode can be as rigorous as the internals. But I'm not really sure. Maybe most generic code should choke on VOID! unless you really wanted to process them... and maybe that added step of having to use GET/ANY is what it should take.

@rgchris suggests some agreement:

I feel :var should be the route to disarming functions/errors as the imperative usage—makes passing them on more intuitive.

I will point out that if we go back to the Rebol2 style, there's nothing stopping versions of the future from opening it back up to the R3-Alpha and Red style. But if people write code expecting GET-WORD!s to return void it will be harder to backpedal later when any significant codebases exist.

This leads me to think we might want to try going back to erroring on VOID! with GET-WORD! and see where the pain points are. There may be tools for addressing that pain that are shaped other ways.

Any objections?

I do wonder if there'd be a way to override this, for example—OF could be allowed to reflect on VOID values as more likely than not, you're going to catch a typo if you get a result you don't expect. type of :thing is common enough and more awkward when expressed as type of get/any 'thing . Not sure if that becomes an extra burden on the function spec.

The typos are a problem, because if (type of firrst [a b c]) is going to be sneaky and void-tolerant, that will silently evaluate to [a b c].

Nevertheless, Ren-C is capable of the experiment:

of: enfixed func [
    'property [word!]  ; soft quote for `(second [length type]) of value`
    :look [<...> <opt> any-value!]  ; hard quote variadic can peek one ahead
    value [<...> <opt> any-value!]  ; normal variadic TAKE if not voided word
][
    reflect either void? get/any try match [word! path!] first look [
        take look
        void
    ][
        take value
    ] property
]

>> type of asdfasdf
== #[datatype! void!]

>> type of second [<a> #b {c}]
== #[datatype! issue!]

The rules for what variadics can and cannot do mirror the rules the evaluator has for itself. So having a quoted variadic feed to "peek" at the next item in the feed and see if it is a WORD! or PATH! evaluating to VOID!, then bypassing that WORD! from evaluation and returning the void datatype can be done.

I doubt it's a good idea to make it do this kind of thing out of the box. But neat that you can do it.

1 Like

All right, I've gone with it. The commits are now in master branch (still pre-stackless) and R3C branch.

Because path processing is a bit costly, I went ahead with a specialization as GET* 'X which acts the same as GET/ANY 'X. It's not quite as cheap as the :X single-token GET-WORD! for accessing a voided word, since it has to fetch a function and run it. But at least it doesn't do path processing, and since it's a native and not a specialization it doesn't have to pay for the (small) specialization overhead. It might be worth it for performance-critical code to have such a thing...I don't know.

This brings back the question of if we want an easier test for whether a variable is voided, e.g. VOIDED? VAR or UNDEFINED? VAR. I had suggested that I thought VOIDED? was clearer by not introducing a new wacky term which might get confused with UNSET? (e.g. actual lack of value, NULL). But then I screwed up a usage of it which gave me second thoughts.

I've often mentioned my torment over whether to stick with "UNSET?" as meaning "set to void", but I really feel that a NULL variable is one that is not set... has no value. Contains nothing you can put in a block. We could also test this with nulled? var. But as with voided-the-word I feel like you could easy get confused with VOID? and VOIDED? as to what it is talking about, while a word that removes you from mentioning the type might be better for comprehension.

Ideas welcome...report any perceived problems or benefits. So far I like it and it doesn't seem to really affect all that much code; many of the problem areas are more on the SET side, which we've decided permit VOID! unless you pre-filter it out with constructs before the SET.


Note: Something additionally I did on master (though not R3C) was some reconciliation of terminology in the internal C sources themselves. So for instance, if an address of a bound word is found in a context and void is legal, I don't call that "Get_Word_XXX"... instead it is "Lookup_Word_XXX". This way any time you see Get_Word_XXX you know that VOID! will be pre-filtered for you and raise an error. I like to keep things in sync like this, to help people reading the C be informed by parity with language behaviors.

1 Like