Function That Errors on NULL (But Passes Thru Everything Else)

If I've done my due diligence in reasoning through a world where NULL is normalized, then being able to have your code assert places where you didn't expect a NULL is pretty important.

Once upon a time, this was called ENSURE:

>> append x ensure select [a 10 b 20] 'c
** Error: e.g. "Couldn't ENSURE you had a value, it was NULL""
** Near: ensure ** select [a 10 ... (blah blah

But that was retaken to be a 2-argument function: a variant of MATCH that triggers an error if the type doesn't match a type or set (e.g. ensure [text! integer!] 1 + 2). It passes the value through if it matches, and raises an error if it does not.

I used that all the time, whereas a single-arity function that checked against NULL seemed less useful. So long as you were throwing in a check, was "it's not null" the only thing you knew about the data? You could even say ensure any-value! ... if that was the case, and that wasn't too bad.

Still...for the heck of it, I went ahead and made a non-null checker and called it... REALLY. Yes, REALLY.

>> append x really select [a 10 b 20] 'c
** Error: e.g. "You REALLY wanted a value, but got back NULL"
** Near: really ** select [a 10 ... (blah blah

I didn't like the name, and never used it. Nulls caused enough problems in those days on their own.
But with Sometimes-Too-Friendly Nulls, callsites could REALLY use this now.

I'd be willing to shuffle names around. But ENSURE became quite entrenched. I'd be loathe to retake it unless a quite good replacement were found.

So, what've we got?

Hm, wait... GOT?

I just wrote that and wondered if it could work:

>> append x got select [a 10 b 20] 'c
** Error: e.g. "Thought you GOT a value, but you got nothin' (it was NULL)"
** Near: got ** select [a 10 ... (blah blah

It's weird, and has a bit of that problem of looking at a word where it doesn't look like a word. Then none of them do if you think too much. Interesting term to know: "semantic satiation"

The idea that GOT is an assertion, a la "I got this", is certainly odd. My leaning is that maybe that suggests it's better as a version of GET that ensures the lookup was not NULL? Otherwise you'd wind up writing oddities like got get. :-/

So this may play a role here in code that wishes to be more cautious about NULLs, as really get. That phrasing makes REALLY look a bit favorable. :-/

TRY would be nice (but it's taken)

I wish we had a short name like TRY available. That would look good:

>> append x try select [a 10 b 20] 'c
** Error: e.g. "You TRY'd but did not succeed, and got a NULL"
** Near: try ** select [a 10 ... (blah blah

But TRY is aiming to be used in error management.

DID would also be nice (but also taken)

>> append x did select [a 10 b 20] 'c
** Error: Thought you DID get a value, but you didn't, it was NULL
** Near: try ** select [a 10 ... (blah blah

DID is serving a good purpose as a complement to NOT, and I'm quite attached to it. It has also made it into the C sources (replacing the !! "operator")

We Never Figured Out The ? Operator

Maybe this is common enough that the ? operator could be taken for it...

>> append x ? select [a 10 b 20] 'c
** Error: e.g. "Asked the big question 'are you a value?' and it wasn't"
** Near: ? ** select [a 10 ... (blah blah

Perhaps not... but it does raise the point that we are asking for sort of a general evaluator operation or something at the callsite to say "I don't want this to be a NULL, here". Maybe unicode or emoji... check mark?

>> append x ✓ select [a 10 b 20] 'c

In fact, just calling it CHECK is an option...though that sounds like something that would be more generic. Or VERIFY? Those sound awfully more grandiose and parameterized than simply seeing if something is null or not. But rebol's fluidity is about letting you name any of these things out of the way if you find them objectionable.

Anyway, REALLY isn't going to get used for anything else (at least no time soon) so it's not like it's hurting anything. I'm just afraid that if it's too gnarly to use that people won't get into the habit of supplementing their code with a nice annotation saying "I expect this to succeed".

The floor is open for thoughts. Short words preferred. Might this be the mysterious use for BE?

>> be 300 + 4
== 304

>> be "I'm an ANY-VALUE!, therefore I am."
== "I'm an ANY-VALUE!, therefore I am."

Doesn't make as much sense at the callsite, though:

>> append x be select [a 10 b 20] 'c
** Script Error: e.g. "A NULL is nothing, and thus cannot BE"
** Near: be ** select [a 10 ... (blah blah

Or...wait...maybe...CAN?

>> append x can select [a 10 b 20] 'c
** Script Error: e.g. "Thought you could get a non-NULL, but you COULDN'T"
** Near: can ** select [a 10 ... (blah blah

Problem being it sounds a little like a question word. append x can any [ ].

Urg. Opinions?

After reading my list, I started feeling like nothing seemed to rival ENSURE. Which gave me the idea of retaking it for ensuring something's not NULL and then doing the match in two steps. So instead of just saying ensure [integer! text!] thing you'd say ensure match [integer! text!] thing.

That looks pretty good to the casual observer, because all failed MATCH returns NULL, right? It's a little more typing, but it's literate--and you can abbreviate it if you do it a lot. (matches: chain [:match | :ensure]) Then you have a fully generic ENSURE as its own part.

Then I tried it and remembered why you can't do it this way. MATCH can be given falsey things to test, like BLANK! or LOGIC! false, or even <opt> to pass NULL itself. When that happens and it matches, the result is converted to NONE.

This means that REALLY MATCH (or after the theorized renaming to ENSURE MATCH) is not a substitute for today's ENSURE behavior. To illustrate the situation as it looks right now:

block: [1 _ 3]

if ensure [blank! integer!] second block [
   print "Why would you test something that FAILs if it doesn't succeed?"
   print "You wouldn't, so no one writes this pattern!"
   print "So no need to worry about VOID!ing a blank"
]

if match [blank! integer!] second block [
    print "...but you might easily write something like this.  :-("
    print "It's good to catch if you do via VOID!"
    print "To mitigate it, you can say `if value? match [...]`"
    print "And there's EITHER-MATCH if your use was more complex"
]

data: really match [blank! integer!] second block
; ^-- you just got NONE and not the BLANK you wanted :-/

I might try to say this fell into the category of me overthinking things, except this is a really easy mistake to make with a matching dialect. And for a double whammy, look at this:

 >> x: really match <opt> null
 == ~

There you have two problems compounded. Firstly MATCH didn't want to give a deceptively falsey result (it did match) so it helped you know with NONE. But even if it had given you a falsey result, REALLY would have rejected it...because the input you are matching was NULL. Yet REALLY-MATCH can do this just fine due to understanding the combination:

 >> x: really-match <opt> null
 ; null

Long story short: ENSURE is probably good how it is. NONE may no longer make sense for MATCH on falsey input vs. raising an error, though. Like I said--these are hard puzzles to fit together.

So maybe keep brainstorming.

>> append x non-null select [a 10 b 20] 'c
** Script Error: e.g. "Being outright literal might not be the worst idea"
** Near: non-null ** select [a 10 ... (blah blah

For that matter, NON could be an anti-ENSURE that is generic.

>> non integer! <foo>
== <foo>

>> non integer! 3
** Script Error: e.g. "you said it was not an integer and it was!"

So non null x would be just an instance of this generalized check. really is 6 characters to that 8, but that's a much more powerful concept, and reads rather unambiguously.


So far I'm liking:

  • NON as the anti-ENSURE, providing non null as one option (along with being generalized)
  • GOT as a shorthand for non null get

After thinking about it, REALLY is probably not as bad as I've made it out to be--but given that it's not as patently obvious as non null you'd have to look it up. By which point, you might as well have looked up THE and learned something much shorter that does the same thing.

1 Like

Anything that is not on a 'standard' keyboard does not belong in code (/ language) unless it is something that is to be printed.

How about affirm ? Could also do where, expect, assume, presume, suppose, must, need.

1 Like

Hmm, MUST is pretty good, hadn't thought of that!

append data must select block item

One thing I've sort of flipped about on is the difference in attitudes between "English can't be a language of relevance in the post-singularity future" vs. "The advent of the Internet and Wikipedia, and English-dominance in programming culture, is hardening the language more firmly than before." Time will tell.

A mixed bag of other options: valid, extant, real, usable, impose

1 Like
viable, must-have, must-be

The construct that has been committed is called MUST

 >> must find "abc" "b"
 == "bc"

 >> must find "abc" "q"
 ** Error: MUST requires argument to not be NULL

But an issue that should make everyone happier is that NULL is becoming accepted fewer places. For instance, APPEND of NULL is an error--not a no-op. You have to MAYBE it to turn it into a VOID

>> append [a b c] find "abc" "q"
** Script Error: append requires value argument to not be null

>> append [a b c] maybe find "abc" "q"
== [a b c]

NULL's emphasis is on being a semi-ornery value that permits word access and is falsey, but isn't really accepted most places...including not being able to be put in blocks. So there should be fewer MUST needed, maybe even more about getting error locality than being necessary to trigger an error ultimately.

2 Likes

Reviewing this two years later...

Technically what I say here is true: you don't have to ensure things aren't NULL any more, because you'll get an error anyway if you try to pass nulls as arguments.

Yet the landscape has shifted: now the issue (if there is still an issue) is having certain spots potentially be void, and opting out when you didn't mean to.

Fortunately that means the map of the problematic territory has shrunk significantly. There are fewer void-returning situations than there are null-returning situations. But branching constructs are candidates for returning void.

>> append [a b c] case [1 = 2 [<d>] 3 = 4 [<e>]]
== [a b c]

Does a VOID-sensing MUST make sense? It's going to read a bit weirdly:

>> append [a b c] must case [1 = 2 [<d>] 3 = 4 [<e>]]
** Error: MUST requires argument to not be VOID

I'd certainly prefer non void on the rare cases you would do this:

append [a b c] non void case [1 = 2 [<d>] 3 = 4 [<e>]]

But furthermore, the design of functions like CASE and SWITCH allows you to tack on a FAIL if they reach the end, which seems more legible to me... same number of characters as MUST if you don't supply a message, but also having the option of giving a message:

append [a b c] case [1 = 2 [<d>] 3 = 4 [<e>] fail]

append [a b c] case [1 = 2 [<d>] 3 = 4 [<e>] fail "your message here"]

So I think the story here has a happy ending, which is that splitting out NULL and VOID intents (with a clear function for converting between them) has basically made MUST obsolete. Constructs are plugging together naturally and giving errors at the right places.

NULL-sensing MUST winds up only having a purpose if you're generating nulls and not passing them anywhere. But the only place I can think of that arising would be like an assert:

str: "abc"
append str "de"
must find str "cd"

I'd rather read that as assert [find str "cd"]... particularly because assert is invisible.

Long Story Short: I'm Killing MUST

I certainly wouldn't add it today. That's a solid argument for deleting it.

It seems to me non null and non void are clearer for the few cases this comes up!

1 Like