(Sorry for the EE term. If you're unfamiliar: Impedance Matching)
A while back I realized that it's best if OBJECT!s, MODULE!s, LET!s, etc. store their contents in lifted representation.
...or rather... they store "historically normal" values in lifted representation (QUASIFORM! and QUOTED!). The unlifted band would then be used for special signals.
One Example: Unlifted TRASH! would denote true unsetness...
And the term actually now fits. e.g. a state of absence of value, beneath the layer of what you could accomplish with SET.
With SET, you can only get "trashed" values:
>> x: ~
== \~\ antiform (trash!) "tripwire"
>> trashed? $x
== \~null~\ ; antiform <-- it's trashed, all right...
>> unset? $x
== \~null~\ ; antiform <-- but it's SET to TRASH!, it's NOT "unset"!
However, special tools and special cases would go beneath SET.
>> tweak $x ~ ; tweak doesn't do the implicit LIFT that SET does...
>> unset? $x
== \~okay~\ ; antiform
There will be shorthands for that like (unset $x). But also, you get this "unset" state as the default states in MAKE FRAME!:
>> f: make frame! negate/
>> unset? $f.number
== \~okay~\ ; antiform <-- actually unspecialized, not specialized to trash!
This brings the long hoped-for distinction between unspecialized values, and values that are purposefully trash! And what was fretted over as being a "hidden bit" is anything but... it's just one "out-of-band" operator away.
e.g. Note that if you use TWEAK with a lifted value, that's just like SET:
>> tweak $x lift ~ ; synonym for (set $x ~)
>> unset? $x
== \~null~\ ; antiform
>> trashed? $x
== \~okay~\ ; antiform
That's Just One Example...
It's the gateway to SETTER and GETTER functions (actually desired by Carl... or setters at least... but he didn't know how to do 'em)
And for stylized setters/getters that do specific things (like type checking) there can be specially understood representations in the unlifted band.
Vocabulary term: I call the multiplexing of lifted and unlifted values together "DUAL REPRESENTATION"
All Of It Is Powered By Common GET and SET Code
This wouldn't work if random places in the code ran off and inspected fields of OBJECT!s literally.
You have to go through some common path. Otherwise you wind up with some callsites honoring the generalized conventions and others not.
It's kind of like how random code in R3-Alpha would ignore the PROTECT status of variables. Ren-C has fought hard long and hard to nail that kind of thing down, and make sure at compile-time that you can be certain the checks aren't being skipped.
Piping everything through common GET and SET code paths ensures that as features like type checks or accessors are added, you don't have rogue code that doesn't honor the convention.
It's been challenging to do this--and right now it's messy and slow--but the commonality means it's worth it to invest in optimizations for that one true path. And Ren-C has plenty of optimization tools at its disposal, which have been evolving over the years... for when the timing is right...
But What About Normal, Boring, Context-Building Code?
Here's an example, just the one on my screen right now.
It's some random code out of the POSIX CALL implementation, related to... forking processes or something:
if (Bool_ARG(INFO)) {
VarList* info = Alloc_Varlist(TYPE_OBJECT, 2);
Init_Integer(Append_Context(info, CANON(ID)), forked_pid);
if (Bool_ARG(WAIT))
Init_Integer(Append_Context(info, CANON(EXIT_CODE)), exit_code);
return Init_Object(OUT, info);
}
Dumb, simple code making an OBJECT! with 2 fields in it, appending those fields (which default to an erased state you have to fill in to be correct code), and then setting the erased cells to mundane values.
If I were to go lockstep through code that looked like this and change it for lifting to appease the common GET and SET code, it would start looking like:
if (Bool_ARG(INFO)) {
VarList* info = Alloc_Varlist(TYPE_OBJECT, 2);
Liftify( // <-- new wart
Init_Integer(Append_Context(info, CANON(ID)), forked_pid)
);
if (Bool_ARG(WAIT))
Liftify( // <-- new wart
Init_Integer(Append_Context(info, CANON(EXIT_CODE)), exit_code)
);
return Init_Object(OUT, info);
}
Liftify adds 2 to the LIFT_BYTE. (Review LIFT_BYTE if you want an introduction to that.)
Liftify also has to check for overflow (e.g. that you're not going past 255 for the LIFT_BYTE value). Maybe the optimizer can figure out it doesn't need that check here? Though I try not to rely on the optimizer too much...
This Parallels The "Too Many ^
" Of Usermode Code
My observation in LIFT the UNIVERSE was that usermode code was becoming contaminated with lifts in places that weren't really the concern of that code. (That's why the robots are celebrating, they're throwing carets in the trashcan...)
Here we're seeing the C code having some of the same problem as having the carets, manifest as calls to Liftify()
. It's getting uglier, and spreading that ugliness around.
Should A CELL_FLAG_DUAL
Be Sacrificed For This?
I don't like wasting the very few CELL_FLAG_XXX. But over time, silly ones have been freed up to give us some wiggle room (e.g. the now-completely superfluous CELL_FLAG_FALSEY).
And maybe this is a really good case where it could be of help to sacrifice one. Since all the GET and SET that's not this kind of stuff is running through centralized code...it could be tolerant of cells in contexts that weren't initialized with CELL_FLAG_DUAL, and just know that those are to be taken literally.
It complicates things a little bit in that one "big, beautiful code path". But as a caller of TWEAK or GET and SET you're insulated from the complication. It's a black box... maybe the cell has CELL_FLAG_DUAL
and maybe it doesn't, you'll never know.
Just Have To Catch Confusions Before They Happen...
Probably best is just to throw in some asserts if you somehow start running through code paths that don't use the common GET somehow, and make sure Type_Of()
and Quotes_Of()
etc will assert on anything that has CELL_FLAG_DUAL.
I'm not sure how many legitimate codepaths there will be that duck the legitimate GET, but there are some reasonable cases (such as the code I give above) that are just doing a simple construction and probably don't need to be more complicated than they already are.
Will It Slow Things Down?
I posted this under Optimization because it's trading off some runtime code to make the C code more tolerable.
But rest assured, this is not the flag test that will be the bottleneck of the system.
(Compared to naive Liftify() everywhere, it probably breaks at least even for not having to do the overflow checking of the LIFT_BYTE.)