Ismissing(x) versus x === missing

Maybe this is a dumb question, but there seems to be a bit of confusion about using ismissing(x) versus x === missing, and isnothing(x) versus x === nothing at [ANN] ShowLint.

I would say that in both cases, the functions are clearer for new Julia users. This is supported by the documentation for missing values (https://docs.julialang.org/en/v1/manual/missing/#Equality-and-Comparison-Operators).

On the other hand, some people say that the function calls are slower, see https://github.com/JuliaLang/julia/issues/35585 and and https://github.com/JuliaLang/julia/issues/27681.

So, is it right that ismissing and isnothing is prefered except when you need to squeeze every last bit of performance?

4 Likes

Using === (or !==) allows the compiler to disregard the other alternative type if the inferred type is e.g. a Union{Nothing, T}. Using == or isnothing does not allow this. https://github.com/JuliaLang/julia/pull/38905 might fix this though.

7 Likes

But Lint is not for new users only. I don’t think isnothing(x) should be advised against x === nothing. I have no intention to follow the suggestion and it would be another annoying red mark in the code when opened in VSC.

2 Likes

I’ve replied to this at [ANN] ShowLint.

@joa-quim To clarify, the linter that @rikh has created is separate from the linter that is built into the Julia VS Code extension.

Is that because if x is of type T (rather than Nothing), isnothing(x) might not return a boolean value? In other words, isnothing(x::T) might be overloaded to not return a boolean? Or perhaps isnothing(x::T) is overloaded and for some reason the compiler cannot infer the return type of isnothing(x::T)? If the compiler can correctly infer the return type of isnothing(x::T), then I wouldn’t think that there would be a performance hit…

1 Like

Sorry for my dumb addition. I use this in my packages for Julia 1.0 compat (which is the implementation in Julia 1.1+):

isnothing(::Any) = false
isnothing(::Nothing) = true

So actually, this:

@inline isnothing(x) = x === nothing

would be the more performant (compiler friendly) version which has no drawbacks. Why is it then implemented like above? Or do I miss anything obvious?

No, it has to be === at every call site in the code. As soon as you hide it behind a function, you’ve lost the === advantage.

1 Like

Even if it’s inlined? :thinking:

This is rather confusing, because Julia is often advertised as having “zero cost abstractions”.

1 Like

Yes.

:wink:

As already been said, there is WIP to fix this (https://github.com/JuliaLang/julia/pull/38905).

2 Likes

Are there really no zero-cost abstractions? I thought some abstractions ‘compiled away’ to produce the same machine code as the ‘inline’ code (like in inlined functions).

Are you including compilation cost?

The TLDR of the talk is that there are 3 classes of cost: runtime, compile, and human. I think Julia does have a better argument for (basically) 0 cost abstractions in some cases. Our macros are a masterpiece of engineering, but still there is some nonzero compile time cost.

1 Like

Admittedly, I didn’t watch the video. When I think of zero-cost abstractions, I basically mean run-time cost. I have the impression that this is pretty common.

Currently Julia has a unary == function defined so that ==(x) is y -> y == x. Why not also have a unary version of === so that isnothing can become ===(nothing), and the same for ismissing? This way nobody would be tempted to use ===(value, nothing) instead of the (superior?) value === nothing because the latter is obviously “more” correct. In comparison, it’s less clear at a glance which of isnothing(value) and value === nothing is preferable.

This does not make any difference in the curried form, since you are already introducing a function barrier which will inhibit constraint propagation anyways. The PR mentioned above has been merged though and will be in Julia 1.7, so the advice of always using x === missing will be obsolete soon.

3 Likes