Why isfoo(x) = x === foo for singleton types?

Base and some libraries introduce a function to test for singleton types (ismissing, isnothing) that is basically an application of ===.

I am curious about the rationale for this API choice. (Not questioning how it is in Base, just trying to decide whether to follow it in my own code).

Just a consistent name with unambiguous intent. You could use === or x -> x === foo for the same effect if you didn’t do something crazy like reassign nothing/missing in a module. The problem was that people weren’t doing that, and they may instead use ==, which fails in the case of missing. x == nothing was also possibly worse for performance, though that section in the Performance Tips is no longer there as of an improvement to propagation in 1.7. x === nothing is still better for type inference than using the generic function isnothing(x) if x is uninferred (holds up in 1.12, though it’s worth noting that the compiler can leverage an inferred Union).

Doing that, in my opinion, is equally likely as reassigning (shadowing) isnothing etc, so it is not something I am considering in API design.

Indeed. Just assume that we are talking about a plain vanilla singleton type that does not redefine ==.

Thanks for your thoughts, it reinforces my belief that adding an isfoo method to my package API is superfluous at this stage of Julia if foo is exported.

(I understand that the methods in Base are here to stay, and the question is not about that, I was just wondering.)

Sample size of 1, but I generally wouldn’t mind if a package doesn’t provide an isfoo function to match a const foo. Nobody ever asked for a consistent ispi from Base, though it wouldn’t be hard to make a package for it. nothing and missing are a bit special because they’re in such a widespread dependency (Base), and checking and filtering would often be done with higher order functions. Routinely writing x -> x===nothing and a bunch of packages implementing their own isnothing functions are horribly redundant, and a package would probably have done isnothing if Base didn’t.

I used Common Lisp before Julia, and for me, writing x -> x ≡ foo is natural. I consider introducing a function for this gratuitous namespace pollution.

There is also the single-argument isequal.

Maybe it has something to do with the fact that === cannot be redefined, thus such a definition cannot be invalidated? Just a guess

If you write isolated x -> x===nothing, you’d be fine, but x -> x===nothing being different anonymous functions in different places needlessly complicates type inference and compilation. Sometimes it’s worth removing that redundancy at the cost of an extra const name. Those names can be optional, there’s a lot still in Missings.jl instead of Base.

Usable, but not the same as the builtin === or propagating it, [] === [] gives a different result from isequal([])([]).

More specifically it’s not a generic function with multiple changeable methods. Compilers can leverage that limitation, and it’s the reason for the better type inference in badly inferred code I mentioned earlier.

I am not quite sure how. Eg if I filter(x -> x === nothing, ...), my understanding is that Julia avoids specialization. At the relevant place it is probably inlined. Is this actually a practical concern?

Yes, I am aware that isequal is not equivalent to == (or === for that matter). But, again, the context is singleton types, so it just falls back to ==, which then falls back to ===.

The user should not worry about adding a tiny bit of extra work for the compiler if it leads to preferable code. That’s why we have a compiler.