Historically, Rebol2 and R3-Alpha wouldn't let you assign UNSET! via a SET-WORD!, regardless of whether it was provided literally or through an evaluation. You had to use SET/ANY:
rebol2>> x: #[unset!]
** Script error: x: needs a value
rebol2>> x: print "hi"
hi
** Script error: x: needs a value
rebol2>> set/any 'x #[unset!]
rebol2>> x
** Script Error: x has no value
At first, I thought Red made a concession in the direct assignment case:
red>> x: #[unset!]
== unset!
red>> x: print "hi"
hi
*** Script Error: x: needs a value
However, that's not what's happening...it's an incompatible interpretation...where Red considers #[unset!] to be the datatype for unset!, and #[unset] (no exclamation point, illegal in Rebol2) is an actual unset value:
red>> type? #[unset!]
== datatype!
red>> type? #[unset]
== unset!
red>> x: #[unset]
*** Script Error: x: needs a value
But I bring it up because at one point in time, Ren-C actually had a specific behavior that it would only allow a SET-WORD! to assign an unset to a variable if it was not the product of a function call. Today this would look something like:
>> x: ~
>> x: print "hi"
** Error: Cannot assign evaluative isotopes to X
>> set/any 'x ~
>> x
** Error: X is ~ isotope (e.g. isotopic BLANK!, represents an unset state)
I've wondered if this rule strikes the right balance. If you really want to deal with isotopic values that are the result of an evaluation, you would use ^META, and get the non-isotopic (QUASI!) form. There'd be several ways of saying it:
>> x: ^ print "hi"
== ~
>> x: ^(print "hi")
== ~
>> x: meta print "hi"
== ~
>> [^x]: print "hi"
== ~
This makes much more sense now than when NULL and "unset" were the same state. And it would help keep isotopes under control.
Implications for the "Only Isotopic ACTION! Runs" Idea
This would mess with my current experimental branch where isotopic actions stored in variables are the WORD!-triggered kind (while regular actions would be inert).
It has FUNC returning isotopic actions, and you'd get an error:
>> foo: func [/refine] [...]
** Error: Cannot assign isotope to FOO with SET-WORD! (see SET/ANY)
Making an exception for action isotopes, and saying they could be assigned without an annotation really feels like it undermines the entire proposal and the safety you'd get from it.
I will note that I really did get a warm fuzzy feeling when the generators produced plain ACTION!, and something had to tip it into being isotopic. But I had to pan /foo: func [... /refine] [...]
in no small part due to leading-slash being "taken" by refinements in function spec and apply.
This made me take a more serious look at the concept of retaking something like ->
. Under the new rules that wouldn't have to finesse the assignment as well:
foo: -> func [/refine] [...]
foo: runs func [/refine] [...] ; for those who dislike symbols
So either of these would act like set/any 'foo isotopic func [/refine] [...]
.
Wait... UNLESS...
I just had a rather compelling thought, that such a tool could only accept isotopic ACTION!, and that this would provide natural guidance not to screw it up... because a plain assignment would error:
>> foo: func [/refine] [...]
** Error: Can't assign isotope ACTION! via SET-WORD!, use -> or INERT
So if all the function generators were committed to returning isotopes, it would be self-correcting. You wouldn't be able to forget to triage the assignment.
>> foo: inert func [x] [print ["x is" x]]
== #[action! [x]]
>> foo
== #[action! [x]]
>> foo: -> func [x] [print ["x is" x]]
== ~#[action! [x]]~ ; isotope
>> foo 10
x is 10
Now THAT is clever. You're forced into triage, and can't forget to do one or the other! Forgetting the annotation was one of the big Achilles heels of making generator products inert by default (hard to find bugs) but this ties that up.
Very promising premise! But there are tricky issues, like how today's METHOD needs to look back to quote what it's being assigned to, in order to know how to bind the value. Maybe it wouldn't need to... if ->
also made that binding connection...and maybe there'd be no such thing as METHOD.
Related Question: What About VOID ?
A corollary to this question is "Should SET-WORD! Stop Treating VOID Assignments as Unsetting Variables"
It might seem obvious to say "yes, that should be an error too". In fact...since I've vetoed the idea of variables actually being able to hold "voidness" it might seem like an even worse sin that should be prohibited... because you're not actually preserving the "integrity" of the assignment.
BUT... when I tried to rule it out I noticed that it was used in frames for things like setting refinements, like:
>> series [a b c]
>> value: 3
>> f: make frame! :append
>> f.series: series
>> f.value: value
>> f.dup: if integer? value [value]
>> do f
== [a b c 3 3 3]
But should the IF evaluate to void...the /DUP would not be set. That not-set state was leading the default behavior for the refinement being not set (to coerce it to NULL in the call).
This is an interesting use case, and it arguably isn't "doing an isotopic assignment" (void is not an isotope, and it has no QUASI! form). It's making a subtle judgment call, and it might be a good one. I'll keep this as-is for now.
Ultimate Goal: Freedom Of Choice
It's still definitely want to make it possible to override these behaviors if you find they get in your way. Certainly we'd need it for Redbol...but it should work at other granularities.
Why shouldn't you be able to say "hey, for just this function's body I want all the SET-WORD!s to assign isotopes without complaint"?