Are nulls the best representation for unused refinements?


#1

The Refinements are their own arguments change is in. And it's a good thing, whose goodness I cannot overstate. It's paying off now and will pay off in the future.

But unifying the refinement and argument has one hitch: what if you want a refinement to be literally BLANK!? I'd hoped it wouldn't be common. But @giuliolunati is using BLANK! to represent a NaN (not-a-number) state...which I have advocated for.

There's a way to do it, though it's kind of ugly. When you say a refinement is [<opt> blank! ...] you are asking for the refinement to be null if unused. This way, blank can be passed as having meaning.

But... that's a bit sketchy. And there's something sketchy about unused refinements being BLANK! in the first place. MAKE FRAME! creates an empty frame with all nulls...for a reason. And those clearly should mean "not specified". For refinements that means not provided. We wind up having to mutate nulls into blanks when the function runs...and it's an "unclean"-feeling thing.

What if /REFINEMENT was used to access refinements?

What's not very likeable is having to use GET-WORD!s all the time, to avoid errors through access of an unset variable:

foo: function [a /ref [integer!]] [
    if :ref [
        print "This is annoying"
        print ["But you only need it for checking" ref]
    ]
]

But something that I've thought about often is the idea that accessing a refinement might look good if it were done with the refinement itself:

foo: function [a /ref [integer!]] [
    if /ref [
        print "This makes sense and actually adds value"
        print ["Still not needed on *every* access" ref]
    ]
]

This might fit into a form of GET that is explicitly for the purpose of NULL checking. We then might make GET-WORD! error on NULLs, so it would be clearer what the purpose of the GET was for (e.g. specifically to suppress function execution). This is something I've felt is pretty necessary for a while because you can't tell at first glance exactly what the point of a GET-WORD! usage is...and here we'd know when it was trying to tell us that the variable may be null.

So to be clear--it wouldn't really have anything to do with refinements. You would be able to use /foo on any word, and have it gloss over the NULL-ness. It would basically be like a GET-WORD! of today. It looks a bit cleaner, if you ask me, and you can type it without shift. :slight_smile:

It would mean losing refinements as an inert class. But as they are PATH!s now, it's not obvious that they should be inert. And with @word, @at/path, @(at group), @[at block] on the horizon...there's several other more clearly inert value options coming.

One downside of making PATH! with blank at the head evaluate would be incompatibility with historical Rebol's inertness of REFINEMENT!. There's no way for Redbol to subvert the evaluator for that. Code would have to be changed to QUOTE the refinement. Changing the way GET-WORD! worked to not allow NULL would similarly be something emulation couldn't override.

Another thing that would be a bit annoying would be using this with a new APPLY syntax, where you wanted to use refinements pass-thru in the expression itself, you'd have to put the refinement in a GROUP!

foo: function [... /dup [integer!]] [
    ...
    apply append [... /dup (/dup) ...]
    apply append [... /dup :/dup ...]  ; or maybe this?
]

That's if you didn't know if it was used or not, since you could use plain dup if by that point you knew it was used. It's not a deal-breaker by any means...pretty easy workaround as such things go. But it's something to bear in mind.


#2

I just noticed I was doing something that wouldn't work in a refinements-are-null world. It seemed like a problem for a minute, until I realized... it wasn't new, and we now have a pretty good solution.

The issue was that in COMPOSE, nulls vaporize. So if you have something doing a code emitter, like:

emit compose [something/refinement plain-arg (/refinement-arg)]

You'll get an error because there will be a missing arg to /refinement. There's no way to put a null into a block.

But...you can get around it with quoting:

emit compose [something/refinement plain-arg '(/refinement-arg)]

Which you probably want to do anyway with generic content being run through evaluation. (What if it had been a GROUP!? What if it was an ACTION!, or WORD!)

Pretty neat.