# Why is a \`isfoo(x) = x === foo\` method defined for some singleton types?

**URL:** <https://discourse.julialang.org/t/why-is-a-isfoo-x-x-foo-method-defined-for-some-singleton-types/134018>\
**Category:** General Usage\
**Tags:** question\
**Created:** [November 21, 2025, 6:50am UTC](https://discourse.julialang.org/t/why-is-a-isfoo-x-x-foo-method-defined-for-some-singleton-types/134018 "2025-11-21T06:50:30Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![Tamas\_Papp](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tamas_papp/32/25949_2.png) [@Tamas\_Papp](https://discourse.julialang.org/u/Tamas_Papp)\
**Post date:** [November 21, 2025, 6:50am UTC](https://discourse.julialang.org/t/why-is-a-isfoo-x-x-foo-method-defined-for-some-singleton-types/134018/1 "2025-11-21T06:50:30Z")

</div>

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

---

<div class="post-metadata">

**Author:** ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)\
**Post date:** [November 21, 2025, 7:20am UTC](https://discourse.julialang.org/t/why-is-a-isfoo-x-x-foo-method-defined-for-some-singleton-types/134018/2 "2025-11-21T07:20:45Z")

</div>

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](https://github.com/JuliaLang/julia/pull/28700), though that section in the Performance Tips [is no longer there](https://github.com/JuliaLang/julia/pull/40517) 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](https://github.com/JuliaLang/julia/pull/44407#issuecomment-1057871117) (holds up in 1.12, though it’s worth noting that the compiler can leverage an inferred `Union`).

---

<div class="post-metadata">

**Author:** ![Tamas\_Papp](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tamas_papp/32/25949_2.png) [@Tamas\_Papp](https://discourse.julialang.org/u/Tamas_Papp)\
**Post date:** [November 21, 2025, 8:15am UTC](https://discourse.julialang.org/t/why-is-a-isfoo-x-x-foo-method-defined-for-some-singleton-types/134018/3 "2025-11-21T08:15:23Z")

</div>

> [@Benny](#):
>
> if you didn’t do something crazy like reassign `nothing`/`missing` in a module

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

> [@Benny](#):
>
> The problem was that people _weren’t_ doing that, and they may instead use `==`, which fails in the case of `missing`.

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

---

<div class="post-metadata">

**Author:** ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)\
**Post date:** [November 21, 2025, 9:05am UTC](https://discourse.julialang.org/t/why-is-a-isfoo-x-x-foo-method-defined-for-some-singleton-types/134018/4 "2025-11-21T09:05:05Z")

</div>

> [@Tamas\_Papp](#):
>
> it reinforces my belief that adding an `isfoo` method to my package API is superfluous at this stage of Julia if `foo` is exported.

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.

---

<div class="post-metadata">

**Author:** ![Tamas\_Papp](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tamas_papp/32/25949_2.png) [@Tamas\_Papp](https://discourse.julialang.org/u/Tamas_Papp)\
**Post date:** [November 21, 2025, 9:16am UTC](https://discourse.julialang.org/t/why-is-a-isfoo-x-x-foo-method-defined-for-some-singleton-types/134018/5 "2025-11-21T09:16:35Z")

</div>

> [@Benny](#):
>
> Routinely writing `x -> x===nothing` and a bunch of packages implementing their own `isnothing` functions are horribly redundant

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

---

<div class="post-metadata">

**Author:** ![bvdmitri](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/bvdmitri/32/22935_2.png) [@bvdmitri](https://discourse.julialang.org/u/bvdmitri)\
**Post date:** [November 21, 2025, 9:21am UTC](https://discourse.julialang.org/t/why-is-a-isfoo-x-x-foo-method-defined-for-some-singleton-types/134018/6 "2025-11-21T09:21:52Z")

</div>

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

---

<div class="post-metadata">

**Author:** ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)\
**Post date:** [November 21, 2025, 9:23am UTC](https://discourse.julialang.org/t/why-is-a-isfoo-x-x-foo-method-defined-for-some-singleton-types/134018/7 "2025-11-21T09:23:05Z")

</div>

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

> [@Tamas\_Papp](#):
>
> There is also the single-argument `isequal`.

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

> [@bvdmitri](#):
>
> Maybe it has something to do with the fact that `===` cannot be redefined, thus such a definition cannot be invalidated?

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.

---

<div class="post-metadata">

**Author:** ![Tamas\_Papp](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tamas_papp/32/25949_2.png) [@Tamas\_Papp](https://discourse.julialang.org/u/Tamas_Papp)\
**Post date:** [November 21, 2025, 9:33am UTC](https://discourse.julialang.org/t/why-is-a-isfoo-x-x-foo-method-defined-for-some-singleton-types/134018/8 "2025-11-21T09:33:27Z")

</div>

> [@Benny](#):
>
> different anonymous functions in different places needlessly complicates type inference and compilation

I am not quite sure how. Eg if I `filter(x -> x === nothing, ...)`, my understanding is that [Julia avoids specialization](https://docs.julialang.org/en/v1/manual/performance-tips/#Be-aware-of-when-Julia-avoids-specializing). At the relevant place it is probably inlined. Is this actually a practical concern?

> [@Benny](#):
>
> `[] === []` gives a different result from `isequal([])([])`.

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

---

<div class="post-metadata">

**Author:** ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)\
**Post date:** [November 21, 2025, 9:57am UTC](https://discourse.julialang.org/t/why-is-a-isfoo-x-x-foo-method-defined-for-some-singleton-types/134018/9 "2025-11-21T09:57:20Z")

</div>

> [@Tamas\_Papp](#):
>
> my understanding is that [Julia avoids specialization](https://docs.julialang.org/en/v1/manual/performance-tips/#Be-aware-of-when-Julia-avoids-specializing). At the relevant place it is probably inlined.

Propagated inlining can be just as performant as method signatures that force specialization in the few exceptional cases. That’s not what I’m talking about though.

For one example, we can’t avoid redundant compilation for semantically distinct anonymous functions, no matter how smart the compiler gets. This following example compiles in a blink of an eye, but it’s not hard to imagine much longer compilation times that are not worth repeating.

```julia-auto
julia> x = [1, nothing, 3];

julia> @time filter(!isnothing, x)
  0.103600 seconds (96.48 k allocations: 4.764 MiB, 99.96% compilation time)
2-element Vector{Union{Nothing, Int64}}:
 1
 3

julia> @time filter(!isnothing, x);
  0.000014 seconds (3 allocations: 144 bytes)

julia> @time filter(y->y!==nothing, x);
  0.018471 seconds (10.36 k allocations: 522.013 KiB, 99.58% compilation time)

julia> @time filter(y->y!==nothing, x);
  0.034947 seconds (10.36 k allocations: 521.841 KiB, 99.71% compilation time)

```

`isequal` does better because it doesn’t make a new function:

```julia-auto
julia> @time filter(!isequal(nothing), x);
  0.031872 seconds (16.94 k allocations: 848.841 KiB, 99.83% compilation time)

julia> @time filter(!isequal(nothing), x);
  0.000010 seconds (3 allocations: 144 bytes)

```

Maybe the most generic way to handle an arbitrary 2-argument comparison is `Base.Fix{2}(!==, nothing)`?

---

<div class="post-metadata">

**Author:** ![Tamas\_Papp](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tamas_papp/32/25949_2.png) [@Tamas\_Papp](https://discourse.julialang.org/u/Tamas_Papp)\
**Post date:** [November 21, 2025, 10:11am UTC](https://discourse.julialang.org/t/why-is-a-isfoo-x-x-foo-method-defined-for-some-singleton-types/134018/10 "2025-11-21T10:11:53Z")

</div>

> [@Benny](#):
>
> we can’t avoid redundant compilation for semantically distinct anonymous functions

Sure, but there is no reason to recompile anonymous functions that were created using _the same syntax_. It’s just currently not implemented, but cf the discussion in [#21113](https://github.com/JuliaLang/julia/issues/21113). Also, for the same callsite, compilation just happens once. Hardly a large cost IMO.

I recognize the current issues and limitations, but I still think there is some superstition about avoiding anonymous functions that is no longer justified.

---

<div class="post-metadata">

**Author:** ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)\
**Post date:** [November 21, 2025, 11:19am UTC](https://discourse.julialang.org/t/why-is-a-isfoo-x-x-foo-method-defined-for-some-singleton-types/134018/11 "2025-11-21T11:19:10Z")

</div>

> [@Tamas\_Papp](#):
>
> there is no reason to recompile anonymous functions that were created using _the same syntax_.

Anonymous function syntax creates a new generic function, and we can add different methods to different generic functions as long as we have references, which doesn’t have to be a `const` name. That’s a very good reason to not automatically treat them as one function. Anonymous function syntax must undergo breaking changes to do so, or we must syntactically specify we are instead accessing a global cache of anonymous functions given a method body, like the suggested `@cached` macro in the discussion. RuntimeGeneratedFunctions.jl implements that with the caveat that closures are not allowed by the underlying `@generated` function.

---

<div class="post-metadata">

**Author:** ![Tamas\_Papp](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tamas_papp/32/25949_2.png) [@Tamas\_Papp](https://discourse.julialang.org/u/Tamas_Papp)\
**Post date:** [November 21, 2025, 11:51am UTC](https://discourse.julialang.org/t/why-is-a-isfoo-x-x-foo-method-defined-for-some-singleton-types/134018/12 "2025-11-21T11:51:21Z")

</div>

> [@Benny](#):
>
> we can add different methods to different generic functions as long as we have references

Certainly, but in most applications in this context (especially inside functions) no such reference escapes. Julia has sophisticated [escape analysis](https://docs.julialang.org/en/v1/devdocs/EscapeAnalysis/) which should be able to figure this out. In which case even the trivial cost of compiling can be spared.

This is now only tangentially related to the original question, but again, I think there is a pervasive mythology surrounding closures in Julia. While in _some_ cases they can be [problematic](https://docs.julialang.org/en/v1/manual/performance-tips/#man-performance-captured), occasionally on this forum discussants recommend avoiding simple, functional style closures that should be innocuous.

---

<div class="post-metadata">

**Author:** ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)\
**Post date:** [November 21, 2025, 1:45pm UTC](https://discourse.julialang.org/t/why-is-a-isfoo-x-x-foo-method-defined-for-some-singleton-types/134018/13 "2025-11-21T13:45:28Z")

</div>

One reason might be that functions can be overloaded for custom types, and this is sometimes done for `ismissing`: [JuliaHub](https://juliahub.com/ui/Search?type=symbols&q=ismissing&u=define) (not too often though).

---

<div class="post-metadata">

**Author:** ![Sukera](https://avatars.discourse-cdn.com/v4/letter/s/ce7236/32.png) [@Sukera](https://discourse.julialang.org/u/Sukera)\
**Post date:** [November 21, 2025, 1:53pm UTC](https://discourse.julialang.org/t/why-is-a-isfoo-x-x-foo-method-defined-for-some-singleton-types/134018/14 "2025-11-21T13:53:56Z")

</div>

I personally like having small functions like `isnothing`. Having worked on a number of codebases that don’t have any of these and instead use field access, direct computation etc. everywhere, it’s honestly harder to read, test & maintain.

Having these functions to me at least gives me vocabulary to talk about the code with colleagues, which is much more important than having “superfluous” oneliner definitions that are widely used in a codebase.

---

<div class="post-metadata">

**Author:** ![Tamas\_Papp](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tamas_papp/32/25949_2.png) [@Tamas\_Papp](https://discourse.julialang.org/u/Tamas_Papp)\
**Post date:** [November 21, 2025, 2:10pm UTC](https://discourse.julialang.org/t/why-is-a-isfoo-x-x-foo-method-defined-for-some-singleton-types/134018/15 "2025-11-21T14:10:47Z")

</div>

> [@Sukera](#):
>
> codebases that don’t have any of these and instead use field access, direct computation etc.

I don’t disagree, but let’s focus on _singleton types_, which is what this topic is about. You can compare them fully with `===`. No field access or direct computation is needed.

`Base` has a _lot_ of singleton types which have no `is...` accessor, eg [the array iteration traits](https://docs.julialang.org/en/v1/base/collections/#Base.IteratorSize). You dispatch or compare them with `===` etc (equivalent when the computation happens in type space).

> [@aplavin](#):
>
> functions can be overloaded for custom types

That is a valid use case, but then it about a more generic API, not just testing if something is a singleton type.

In any case, I think `isnothing` is a vestigial feature (that’s OK, now it is here to stay, but newer APIs do not need something like this — which answers my question), `ismissing` can be made a use case for when one generalizes the concept of “missingness”.

---

<div class="post-metadata">

**Author:** ![pdeffebach](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/pdeffebach/32/10320_2.png) [@pdeffebach](https://discourse.julialang.org/u/pdeffebach)\
**Post date:** [November 21, 2025, 2:21pm UTC](https://discourse.julialang.org/t/why-is-a-isfoo-x-x-foo-method-defined-for-some-singleton-types/134018/16 "2025-11-21T14:21:51Z")

</div>

For `ismissing`, there was a [PR](https://github.com/JuliaLang/julia/pull/44407) to use `ismissing` everywhere in anticipation that there might, one day, be multiple `missing` types.

---

<div class="post-metadata">

**Author:** ![mbauman](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mbauman/32/31082_2.png) [@mbauman](https://discourse.julialang.org/u/mbauman)\
**Post date:** [November 21, 2025, 2:41pm UTC](https://discourse.julialang.org/t/why-is-a-isfoo-x-x-foo-method-defined-for-some-singleton-types/134018/17 "2025-11-21T14:41:13Z")

</div>

I suspect part of the reason for this is `isnan`. For that operation, _none_ of `==` or `isequal` or `===` are what you want… because floating point. Now, yes, I know, that’s not your question, but many folks moved from using `NaN` sentinels to `missing`s — be it Julia’s `ismissing` or another language/framwork’s `isna` or `is.na`. And they had it (rightly!) ingrained to never check `== NaN`. So checking `=== NA` probably _felt_ similarly wrong. It’s all about the feels! Once you have a function named `isnan` and get used to using `filter(!isnan, _)`, it’s only natural to think that there should be an `ismissing` or `isnothing`, whether it’s actually necessary or not.

For Julia itself, there’s a question of whether you should use `==` or `===`, and there have historically been some surprising performance effects to getting that choice wrong. And you _can’t_ write `===(nothing)`.

Using a base function means that _someone_ (probably multiple someones, actually) has thought about what the best implementation should be — and I don’t need to do that thinking!

---

<div class="post-metadata">

**Author:** ![Sukera](https://avatars.discourse-cdn.com/v4/letter/s/ce7236/32.png) [@Sukera](https://discourse.julialang.org/u/Sukera)\
**Post date:** [November 21, 2025, 5:24pm UTC](https://discourse.julialang.org/t/why-is-a-isfoo-x-x-foo-method-defined-for-some-singleton-types/134018/18 "2025-11-21T17:24:25Z")

</div>

> [@Tamas\_Papp](#):
>
> I don’t disagree, but let’s focus on _singleton types_, which is what this topic is about. You can compare them fully with `===`. No field access or direct computation is needed.

Sure, but why should singleton types be more special here?Just for consistencies sake, I’d still introduce that oneliner. It’s not like we’re only looking at `isnothing` in isolation, otherwise I’d completely agree with you.

Not to mention, currently things like `x -> x === Foo` always introduce a new anonymous function, and always having to visually parse that this particular anonymous function is functionally equivalent to `isfoo`, for me at least, adds some overhead. Add up enough of that kind of small overhead and it’s harder than necessary to follow the intent of the code 🤷

---

<div class="post-metadata">

**Author:** ![CameronBieganek](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cameronbieganek/32/6915_2.png) [@CameronBieganek](https://discourse.julialang.org/u/CameronBieganek)\
**Post date:** [November 21, 2025, 7:24pm UTC](https://discourse.julialang.org/t/why-is-a-isfoo-x-x-foo-method-defined-for-some-singleton-types/134018/19 "2025-11-21T19:24:33Z")

</div>

I think that `isnothing` and `ismissing` are cases of Don’t Repeat Yourself, even though the repeated code is quite short. I also think they’re more readable.

---

<div class="post-metadata">

**Author:** ![matthias314](https://avatars.discourse-cdn.com/v4/letter/m/a88e4f/32.png) [@matthias314](https://discourse.julialang.org/u/matthias314)\
**Post date:** [November 22, 2025, 1:00am UTC](https://discourse.julialang.org/t/why-is-a-isfoo-x-x-foo-method-defined-for-some-singleton-types/134018/20 "2025-11-22T01:00:32Z")

</div>

> [@Tamas\_Papp](#):
>
> `filter(x -> x === nothing, ...)`,

Packages may implement dedicated `filter` methods for certain functions. Anonymous functions cannot be matched, but

```julia-auto
filter(::typeof(isnothing), c::SomeCollection)
filter(::Base.Fix2{typeof(===), Missing}, c::SomeCollection)

```

can.
