What to call a blank-in, null-out annotation?

The convention of BLANK-in, NULL-out...in which routines act as no-ops when they receive a blank...is extremely common. So far it's meant you wind up writing things like:

exists?: function [file [blank! file!]] [
     if blank? file [return null]
     ... // actual code

With @rgchris's recent updating efforts running against the "more nulls" convention, I've tried to emphasize that following this convention is what makes it all "work". It's why COLLECT returning NULL becomes smart and palatable. The conditional falsey-ness of NULLs, with the ease by which TRY can turn it into a "friendlier" nothing that most routines are willing to pass on, is an elegant design.

There's a lot of upside to this. Especially if the evaluator knows a parameter means "don't run if blank"...it can avoid the processing and overhead involved in a call. That's on top of all the savings you get by not generating the empty blocks or strings in the first place, then having to GC them. It's better, I promise!!!

But it only works if the convention is pervasive. So it has to be easier...what should the annotation be called? If it were Zen it could be <unask> or <mu> for "unasking a question". :slight_smile:

I think a good choice is to just use a TAG! that says <blank> vs. the datatype BLANK!.

exists?: function [file [<blank> file!]] [
     ... // actual code, that never receives a blank
     ... // because the function won't be run if it gets one!

It's similar to an existing convention for return types. When you say return: <void>, it means "no matter what is returned, make it a void, so other results don't leak out". While return: [void!] is just like returning any other datatype...e.g. "void is one of the acceptable return types, check that the routine doesn't return anything else".

So my thought here is that it's learnable that <blank> means "special blank". You'd find out pretty quickly there's a difference from plain blank! in your typeset, because your function won't run at all if it is blank!

I'd be wary of a name that didn't mention blank at all, and talked only about the effects. Like that it returns null, or something:

exists?: function [file [<nullifies> file!]] ...
exists?: function [file [<revokes> file!]] ...
exists?: function [file [<retracts> file!]] ...

To my mind, the word blank has to be in there to draw attention to blank's role. But more than one word seems it would get clunky. <no-op-if-blank>, <blank-revokes>, <blank-in-null-out> ad nauseum. There are some other one-word options, like <blankable> or <blankifies> but I'm not sure what it adds.

So my vote is plain <blank>. Admittedly, it is subtle to just be "special blank!", and leave it up to the imagination of what's so special about that blank. But I think in the long run, the cleanness and appreciation for less typing outweighs its somewhat enigmatic nature.

Anyone object?

No objection here. Seems fine but I'll reserve judgment for those like @rgchris who are busy absorbing and applying these new conventions.

1 Like

We have another question, which is when should a blank not opt out. Branches of conditionals? Bodies of loops?

condition: _
body: [...]
while condition body // why shouldn't this be a no-op?

condition: [...]
body: _
while condition body // why shouldn't this be a no-op?

Blanks are the intentional form of "I meant to do that" nothing, while to get nulls you'd be working with unset variables, or function calls...and the design is being careful to have functions return nulls vs. blanks on "nothing found".

One critique could be that it's not as obviously safe when no one checks the result...like with a WHILE, there's nothing to error on a null. Though I would ask: why did you use BLANK! if you didn't want the smooth and errorless opting out? Why not use null, so the variable was unset?

This is quite useful for code golf. But also for building library routines...the new efficient trick of not calling function bodies at all when parameters are marked <blank> makes it desirable to take advantage of it wherever possible. I feel like you should have more of a reason not to allow blanks to opt out of arguments and ask the function not to be called, than to need a reason to have it.

Something has kind of come to me while thinking about when something would take a blank argument vs. a null one.

  • A null passed to an <opt> argument means I am opting out of this argument...but I still want to receive the result of the operation. e.g. you don't want append block if false [] to return null and not follow the APPEND protocol.

  • A BLANK! passed to a <blank> argument (or whatever it is called) means I am opting out of this operation.

This helps provide some guidance on things like DELIMIT and its "delimiter" parameter. It should take a NULL, not a BLANK! to mean "no delimiter". Because even without a delimiter, you are asking it to do some work and give you a return result.

The lunatic fringe: Should APPEND not run if the series is blank?

A classic example of "what seems like a bad idea" has been the concept of APPEND to a blank series opting out:

data: _
append data value // it could do nothing and return null, but who checks it?!

It's clear this provides more flexibility than erroring. But is it way too dangerous?

Maybe not, if blanks aren't very common.

Note that refinement arguments no longer default to blank, they're unset. It's becoming extremely rare for routines to return blanks...you might find them if you're doing structural work on blocks. Most things return null when they fail, and no one is suggesting append null value being anything but an error.

This is one of those "sounds kinda scary" ideas that also "sounds kinda cool". If BLANK! can shift its role to being the "reified black hole" and acting like a sort of generalized /dev/null, that could be interesting.

For the moment, I kind of feel that operations for which people usually don't check a result, and expect a mutation, it is probably still better to err on the safe side. And it's always better to err on the safe side...as what's an error today could be eased later, while going to other direction is harder on people.

But it's something to think about--in light of the point about what blanks and nulls mean.