# X === nothing vs x isa Nothing

**URL:** <https://discourse.julialang.org/t/x-nothing-vs-x-isa-nothing/64546>\
**Category:** General Usage\
**Created:** [July 13, 2021, 12:53am UTC](https://discourse.julialang.org/t/x-nothing-vs-x-isa-nothing/64546 "2021-07-13T00:53:54Z")\
**Posts on this page:** 1\
**Showing post:** 13

<div class="post-metadata">

**Author:** ![mkitti](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mkitti/32/12459_2.png) [@mkitti](https://discourse.julialang.org/u/mkitti)\
**Post date:** [July 14, 2021, 9:14pm UTC](https://discourse.julialang.org/t/x-nothing-vs-x-isa-nothing/64546/13 "2021-07-14T21:14:33Z")

</div>

On Julia master branch (to be Julia 1.7) at the moment,

```julia
isnothing(x) = x === nothing

```

Previously (Julia 1.1 to 1.6, inclusive) it was

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

```

> <https://github.com/JuliaLang/julia/pull/38905>
>
> This PR propagates \`Conditional\`s inter-procedurally when a
> \`Conditional\` at re…turn site imposes a constraint on the call arguments.
> When inference exits local frame and the return type is annotated as
> \`Conditional\`, it will be converted into \`InterConditional\` object,
> which is implemented in \`Core\` and can be directly put into the global
> cache. Finally after going back to caller frame, \`InterConditional\` will
> be re-converted into \`Conditional\` in the context of the caller frame.
> 
> ## improvements 
>  
> So now some simple "is-wrapper" functions will propagate its constraint
> as expected, e.g.:
> \`\`\`julia
> isaint(a) = isa(a, Int)
> @test Base.return\_types((Any,)) do a
> isaint(a) && return a # a::Int
> return 0
> end == Any\[Int\]
> 
> isaint2(::Any) = false
> isaint2(::Int) = true
> @test Base.return\_types((Any,)) do a
> isaint2(a) && return a # a::Int
> return 0
> end == Any\[Int\]
> 
> function isa\_int\_or\_float64(a)
> isa(a, Int) && return true
> isa(a, Float64) && return true
> return false
> end
> @test Base.return\_types((Any,)) do a
> isa\_int\_or\_float64(a) && return a # a::Union{Float64,Int}
> 0
> end == Any\[Union{Float64,Int}\]
> \`\`\`
> 
> (and now we don't need something like #38636)
> 
> ## benchmarks
> 
> A compile time comparison:
> \> on the current master (82d79ce18f88923c14d322b70699da43a72e6b32)
> \`\`\`
> Sysimage built. Summary:
> Total ─────── 55.295376 seconds
> Base: ─────── 23.359226 seconds 42.2444%
> Stdlibs: ──── 31.934773 seconds 57.7531%
> JULIA usr/lib/julia/sys-o.a
> Generating REPL precompile statements... 29/29
> Executing precompile statements... 1283/1283
> Precompilation complete. Summary:
> Total ─────── 91.129162 seconds
> Generation ── 68.800937 seconds 75.4983%
> Execution ─── 22.328225 seconds 24.5017%
> LINK usr/lib/julia/sys.dylib
> \`\`\`
> 
> \> on this PR (37e279bce7136e48f159811641b68143412c3881)
> \`\`\`
> Sysimage built. Summary:
> Total ─────── 51.694730 seconds
> Base: ─────── 21.943914 seconds 42.449%
> Stdlibs: ──── 29.748987 seconds 57.5474%
> JULIA usr/lib/julia/sys-o.a
> Generating REPL precompile statements... 29/29
> Executing precompile statements... 1357/1357
> Precompilation complete. Summary:
> Total ─────── 88.956226 seconds
> Generation ── 67.077710 seconds 75.4053%
> Execution ─── 21.878515 seconds 24.5947%
> LINK usr/lib/julia/sys.dylib
> \`\`\`
> 
> Here is a sample code that benefits from this PR:
> \`\`\`julia
> function summer(ary)
> r = 0
> for a in ary
> if ispositive(a)
> r += a
> end
> end
> r
> end
> 
> ispositive(a) = isa(a, Int) && a \> 0
> 
> ary = Any\[\]
> for \_ in 1:100\_000
> if rand(Bool)
> push!(ary, rand(-100:100))
> elseif rand(Bool)
> push!(ary, rand('a':'z'))
> else
> push!(ary, nothing)
> end
> end
> 
> using BenchmarkTools
> @btime summer($(ary))
> \`\`\`
> 
> \> on the current master (82d79ce18f88923c14d322b70699da43a72e6b32)
> \`\`\`
> ❯ julia summer.jl
> 1.214 ms (24923 allocations: 389.42 KiB)
> \`\`\`
> 
> \> on this PR (37e279bce7136e48f159811641b68143412c3881)
> \`\`\`
> ❯ julia summer.jl
> 421.223 μs (0 allocations: 0 bytes)
> \`\`\`
> 
> ## caveats
>   
> Within the \`Conditional\`/\`InterConditional\` framework, only a single
> constraint can be back-propagated inter-procedurally. This PR implements
> a naive heuristic to "pick up" a constraint to be propagated when a
> return type is a boolean. The heuristic may fail to select an
> "interesting" constraint in some cases. For example, we may expect
> \`::Expr\` constraint to be imposed on the first argument of
> \`Meta.isexpr\`, but the current heuristic ends up picking up a constraint
> on the second argument (i.e. \`ex.head === head\`).
> \`\`\`julia
> isexpr(@nospecialize(ex), head::Symbol) = isa(ex, Expr) && ex.head === head
> 
> @test\_broken Base.return\_types((Any,)) do x
> Meta.isexpr(x, :call) && return x # x::Expr, ideally
> return nothing
> end == Any\[Union{Nothing,Expr}\]
> \`\`\`
> 
> I think We can get rid of this limitation by extending \`Conditional\` and
> \`InterConditional\`
> so that they can convey multiple constraints, but I'd like to leave this
> as a future work.
> 
> EDIT: 3df027a implements the "pick up" logic within a caller
> (i.e. within \`abstract\_call\_gf\_by\_type\`), which allows us to choose a
> constraint more appropriately, and now the \`Meta.isexpr\` case is fixed.
> Still there is a limitation of multiple conditional constraint
> back-propagation; the following broken test case will explain the future
> work.
> \`\`\`julia
> is\_int\_and\_int(a, b) = isa(a, Int) && isa(b, Int)
> @test\_broken Base.return\_types((Any,Any)) do a, b
> is\_int\_and\_int(a, b) && return a, b # (a::Int, b::Int) ideally, but (a::Any, b::Int)
> 0, 0
> end == Any\[Tuple{Int,Int}\]
> \`\`\`
> 
> \---
> 
> \- closes #38636
> \- closes #37342

> <https://github.com/JuliaLang/julia/commit/36e7e8a10f4f3d0ddf85ff15bd7439c3927622bf#diff-177aa526ed7d10bcf44dd31c5409c4a937cfab2f336cb1a8055b4c575f95da85>
>
> note that the changes for \`isnothing\` and \`ismissing\` aren't necessary,
> but they… reduce the number of method definitions for good reason;
> the less the number of methods they have, the better we can
> back-propagate type constraints, because even after a package defines
> their own new methods for them we can keep to use our \`InterConditional\`
> logic as far as far as the number of methods is
> \[≤3\](https://github.com/JuliaLang/julia/blob/5c6e21edbfd8f0c7d16ea01c91d1c75c30d4eaa1/base/compiler/types.jl#L119)

---

_[View the full topic](https://discourse.julialang.org/t/x-nothing-vs-x-isa-nothing/64546)._
