[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.

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 ?);
1 Like

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.

2 Likes

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

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.