metaeducation / ren-c

Library for embedding a Rebol interpreter into C codebases
GNU Lesser General Public License v3.0
126 stars 27 forks source link

Changes to FRAME! and Refinement Model, + REFRAMER #1094

Closed hostilefork closed 3 years ago

hostilefork commented 3 years ago

This is a set of changes that will cause a bit of disruption for specializations, but paves the way for new tools.

One of those tools is REFRAMER, explained here:

https://forum.rebol.info/t/introducing-reframer-close-cousin-to-enclose/1395

But what changes here are a couple of things...

MAKE FRAME! now has ~undefined~ in the unspecialized slots, not NULL

Previously, it was hard to specialize out arguments. Now it is easier, because when you assign NULL to something it takes that to mean it is specialized.

 >> parameters of :append
 == [series value /part /only /dup /line]

 >> append-no-part: specialize 'append [part: null]
 >> parameters of :append-no-part
 == [series value /only /dup /line]

This turns it around so that ~undefined~ is now the difficult state to specialize with. But that's only one labeled void... you can still specialize with voids of other labels.

(Historical Note: When MAKE FRAME! was created, there was no VOID!. So NULL was what was used. Had VOID! been around, it probably would have been the natural choice at the time.)

Refinements With No Arguments Will be # or NULL

Due to the previous point about NULL being considered a specialized state, this means assigning refinements can be pretty easy.

 f: make frame! :append
 f/only: if condition [#]  ; null if not true, # if true
 a: make action! f  ; will see the NULL or # as specialized

It's easy, though I know it's not as easy as just being able to assign a LOGIC! to f/only. And I also know that # doesn't carry information of the name of the refinement, which breaks the idea of being able to conveniently use it for chaining to call another function with that refinement name. (You have to say whatever/(if refinement [/refinement]) instead of just whatever/(refinement)).

But this is for a very good reason that has been thought about deeply. Firstly, a refinement which takes a LOGIC! is a very conceptually different thing from a used-or-not refinement.

 foo: func [/ref [logic!]] [ref]

 true = foo/ref true
 false = foo/ref false
 null = foo

That's a tristate. And we want the NULL state to mean "unspecified" across the board.

So when it comes to the answer for a refinement with no arguments, it's going to have to be NULL-and-something. And we don't want to have to mutate the result to canonize it.

This gives people something that's easy to set to, cheap cell bits, and truthy.

Let me know if this causes any confusion.