[syntax] Inverse null coalescing operator

As there already exists a nullish coalescing operator (??), this implies a use case for an inverse nullish coalescing operator, which would still be a binary operator returning the second operand if the first one is not nullish (in contrast with ??).

Sample use-case: when fetching a possibly nullish relation:

let retrievedModel: Model | null = idOrNull === null ? null : await database.fetch<Model>(idOrNull); // the way it can be achieved now
let retrievedModel: Model | null = idOrNull !? await database.fetch<Model>(idOrNull); // inverse null coalescing operator !?

An analogic use case would involve a value of undefined or a union of undefined and null, analogically to the null coealescing operator.

5 Likes

The example provided is a very bad pattern. idOrNull semantically means an identifier for an object in the database or not, so why would you use this in a object that is specifically typed Model | null (this typing implying the database found an object based on the ID or not)? In general, people write

async function findModel(id: string | null): Promise<Model | null> {
  if (id === null) return null;
  // Search the database with the ID
}

which abstracts the database layer from internal logic.

Aside from the example,

  1. the nullish operator was created because of the annoyance of null when handling ternary assignment.

  2. and this (from wiki for null pointers)

In 2009 Tony Hoare (C.A.R. Hoare) stated that he invented the null reference in 1965 as part of the ALGOL W language. In that 2009 reference Hoare describes his invention as a "billion-dollar mistake":

I call it my billion-dollar mistake. It was the invention of the null reference in 1965. At that time, I was designing the first comprehensive type system for references in an object oriented language (ALGOL W). My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn't resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.

By the way, here is a small exercise: Can you return null in javascript without using null in the language? (No JSON/RegExp/eval allowed)

I would argue with you on the 'very bad practice' thing. Not even mentioning that your point does not apply if the function is not implemented by hand, but provided externally (and therefore limits you to handle idOrNull being null inside it), the snippet you provided is incorrect (variable names not matching => undefined variable consider), it is also buggy in a specific case (hypothetically also consider id being 0, which would be treated like a nullish value by your code; you should have used a strict comparison operator, ===).

My example might be a specific use case, but it is just an example, one of many you can think of. In this case, it might be an API handler that accepts an optional (here, either null or Number) request parameter. And of course, findModel is just a logical placeholder for an external module, so there is no point to modify it's code, as well as my whole snippet being just a logical shorthand to provide an overview of an arbitrary use case for such an operator.

I also do not quite see your point regarding the citation, as null already exists and therefore it seems completely uncorrelated with the topic.

1 Like

Fixating on incorrect code is useless, you know what the example meant. Also, I stated abstracting from the database layer. If the function is defined externally, write a wrapper. This is best practice since external implementation details are then hidden in that function.

The point is null is not something you ever want. If you did the exercise, you would realize JS doesn’t give you null anywhere intentionally (without the exceptions I mentioned) (there is a way by the way but you would have to be a computer to find the solution). Your proposal would be the first, but as mentioned, this is bad practice.

tldr, this is bad practice. (Also, reverse nullish would give you null or undefined, not just null)

(EDIT: you should see my proposal about handling nullish values in function arguments)

By the way, here is a small exercise: Can you return null in javascript without using null in the language? (No JSON/RegExp/eval allowed)

Object.getPrototypeOf(Object.prototype)

3 Likes

What I am actually missing is a static-function equivalent to the nullish-coalescing on method calls. Where

 somethingOrNull?.method()

desugars to

somethingOrNull != null ? somethingOrNull.method() : null;

we have nothing for

somethingOrNull != null ? func(somethingOrNull) : null;

One can currently use && but that requires repetition of the whole somethingOrNull expression (which might not be a simple variable) and only works if the something is not a primitive that can have a falsy value. I'd love to be able to write just

func( somethingOrNull ?);
2 Likes

One for the books! (Also disgusting lol)

This certainly has its uses, but in OOP, if there is no method, then that generally means a different type of object. One should always check the object before using a method (regardless if it’s there or not). This check can be implicit of course (meaning you know it’s there) but other than that, your use case would imply dynamic declarations of methods which IMO is also very bad practice.

What I am actually missing is a static-function equivalent to the nullish-coalescing on method calls.

As the |> pipeline operator can emulate the style of calling a method on a value

value |> methodIsh // value.method()

maybe a nullish-coalescing style pipeline operator would be useful?

value ?> methodIsh // value?.method()
1 Like

With such a solution, aren't we actually approaching an implementation similar to the operator I suggested? Using it, your second snippet could be re-written as value !? methodIsh(value) or, in conjuction with the pipeline operator, as value !? value |> methodIsh and the benefit of such a usage would be having a call to methodIsh only if value is not nullish.

One difference with a pipeline style operator is that it can be chained

let maybeComments = maybeId
                      ?> lookUpUser
                      ?> getLatestPost
                      ?> getComments

With !? :

let maybeUser = maybeId !? lookUpUser(maybeId);
let maybePost = maybeUser !? getLatestPost(maybeUser);
let maybeComments = maybePost !? getComments(maybePost)

Literally what's proposed here: https://github.com/tc39/proposal-pipeline-operator/issues/159

Edit: @ you and practically everyone else in this thread.

3 Likes

@claudiameadows excellent! Not a bad sign when the same idea pops up in two separate places. Hopefully that gets traction

1 Like

I think for parity with the other nullish/optional operators, the expression would need to evaluate to undefined rather than null for the lhs-nullish branch.

A couple benefits to having an operator for this (in addition to optional pipeline chaining, not instead of):

  • operator would work with RHS template strings / arithmetic without needing an extra function
  • it should be possible to chain more than one of these "type guards" together
    and do something with more than one value.
  • no need to create a wrapper function or hope partial application is implemented
    to call a function with multiple arguments

I think I posted a near duplicate of this yesterday with some additional hypothetical examples
(as ?::, but token doesn't matter other than needing to have a ? in it somewhere)

x ?:: y ?:: `${x} ${y}` //this would be ok if evaluated l-r right?
(a ?:: a + 2) ?? 0  // in combination with nullish coalescing
1 Like

I agree with this concept, but I have some specific reasoning about the operator names.

It often happens that the key might be a nullish foreign key ID. The database allows nullable foreign keys, and the code will naturally deal with this. We're looking for an elegant syntax for a common case.

Also note that pipelines do not address composite keys, where we would either need this operator anyway or the pipeline needs to support arbitrary number of arguments in an equivalent manner, which I am unable to see, since that would be a tuple to start the pipeline, but we want to coalesce the arguments in this way.

Let's not get into the fact that ES runs into the lack of an Option type the more it wants to become functional. Call it null or call it "potato", the lack of Option has made null/nullish as the de facto sentinel for None. At this point, it's more realistic to ask for completeness with a complement to the implemented ?? operator for nullish, than to touch radically replacing null/nullish with Option and its methods. And we can't get rid of null/nullish without such an Option.

Just ran into this again, would definitely be nice to be able to do:

a !? b
a !?= b
2 Likes

I ended up here (and would have suggested this idea otherwise) because I found myself with precisely the issue that this operator would neatly address: I want to coalesce/chain into an expression which uses a value (if it exists), else short-circuit and return null/undefined, and also more generally not have to keep track of which (or, perhaps worse, check verbosely for both separately). I think that the 'Celsius → Farenheit' example in dustbort's linked thread is a sensible such use case: one value depends upon another; we have good reason to preserve nullish coordinate values; and we really don't want to be instantiating a Celsius class and optionally chaining a toFarenheit method for such a simple system.

!? as a 'non-nullish coalescing' (or 'existence coalescing'?) operator fits a niche—nay, a hole—in the language: it's a completely natural complement/inversion to ??, pairs consistently with ==/!=, introduced no syntax/parsing ambiguities, and, as far as I can tell, only obviously precludes ! as a potential future postfix unary operator (in conjunction with ternary or optional chaining). Easy parsing, too, since it branches off ! for a maximum of one character before resolving, and doesn't mess with ternary parsing at all.

?! would make succinct nullish handling slightly more robust (see: the many threads about null/undefined handling (complete with obligatory mentions of document.all)), and more importantly, it makes the intent of such code much clearer: && communicates "must be truthy," while !? would communicate "it just has to exist"—much as ?? communicates a default/fallback expression ("if it doesn't exist then...") distinctly from || ("if it isn't truthy then...").

One more benefit that I think is worth mentioning and makes this proposal more than syntactic sugar: it obviates repetition of the first operand to preserve its value: the only equivalent that we seem to have for the proposed a !? b right now is a ternary on a != null ? a : b (or equivalent for the condition), which requires repeating a. From what I can tell, that's pretty much the whole case behind the pipe operator—not needing intermediate assignments—and I think that it's a good reason. Being able to cleanly carry a value within an expression without needing to give it a name makes a massive difference for clarity and maintainability, in the 'if considered harmful' sense, much as the ternary operator lets us avoid repeating the left-hand assignment, or calling function/method (and any other arguments), or whatever context the ternary is being used in.

For completeness, while !?= might seem a little 'cute', I think it might situationally be serve as a convenient, succinct, clear 'unset'/'reset to default' (if initialised) operator. One might wish to be able to reset a field (which may or may not be 0 or false, so &&= wouldn't work, in the same way that one can't use && for robust non-nullish coalescing) to some default value (which could be anything, not just null or undefined), but only if the value has been set (so ||= wouldn't work, because it would reassign nullish values). Perhaps this might occur for an update method in a class instance which hasn't finished initialising—or something vaguely like that, where multiple asynchronous parts are interoperating and smooth, clean sequential execution can't be guaranteed. Alternatively, reverting an optional field to a default/provided value, but only if it's supplied in the first place. I can imagine applications where the resulting code is cleaner and more legible, and despite my efforts I don't see any significant potential downsides. Am I missing any?

Re. the pipe argument: I really like the pipe operator proposal, but it shouldn't subsume this suggestion, just as it shouldn't replace &&; a proposed 'base-level' operator shouldn't be bundled into the pipe mechanism in the same way that operator-assignment shouldn't be bundled with the operator itself. Piping is already a variation of sorts on expression chaining using logical operators, optional chaining, and such: I like the idea of a complementary 'optional pipe' operator, but having a 'regular' non-nullish coalescing operator (or 'existence coalescing operator') would still serve sensible use cases and 'fill out' a natural, existing design space in the language.

As a side-note, for posterity, I agree that ?? is an unfortunate token, but for a different reason: I think that !? makes the more natural choice for the nullish-coalescing operator:

  • a?.b → a? ("a exists / has a value?") If so, then .b
  • a ? b : c → a? If so, then b, otherwise (:) c
  • a ?? f(a) → If a has a value (isn't nullish), then f(a) (akin to && returning the second operand if the first is truthy)
  • a !? b → If a not (!) exists (?) then b instead (akin to || returning the second operand if the first is falsey)

The ternary operator already associates the immediate succedent of ? with truthy values (for the first operand); optional chaining only proceeds the chain if the value 'exists'. (We could have 'nullish' and 'exists' as coordinates to 'falsey' and 'truthy'.) For what it's worth, I more naturally read a !? ( ... ) as "No a?! In that case, (...)"

Considering that optional attribute access is ?. (rather than .?), I think that ?& and ?| would make for more coherent aliases (at best) for non-nullish coalesceing and nullish-coalescing respectively: 'exists and' and 'exists or/else' respectively. Since we already have ??, though, I think that's no longer likely.

Still, the next best option still to be 'completing the set' with !? and !?=, n'est-ce pas?

1 Like