Symbols are immutable and should be compared using ===.
Is that for the same reason that it was recommended to use === when checking for missing or nothing?
And following the merge of #38905 which fixed the performance issue with ismissing(...) and isnothing(...), will it still make a difference to use === for symbols?
Unfortunately, the Julia manual uses a automatic generator of HTML pages which creates a not very good search bar. Searching ===, (===), or "===" gives no results. This is a known problem.
help?> Symbol
search: Symbol
Symbol
The type of object used to represent identifiers in parsed julia code (ASTs). Also often used as a name or
label to identify an entity (e.g. as a dictionary key). Symbols can be entered using the : quote operator:
[...]
Symbols are immutable and should be compared using ===. The implementation re-uses the same object for all
Symbols with the same name, so comparison tends to be efficient (it can just compare pointers).
[...]
julia> x = Union{Missing,Float64}[1.0]
1-element Vector{Union{Missing, Float64}}:
1.0
julia> function foo(x)
s = 0.0
x1 = x[1]
ismissing(x1) ? s : s + x1
end
foo (generic function with 1 method)
julia> foo(x)
1.0
julia> @code_warntype foo(x)
Variables
#self#::Core.Const(foo)
x::Vector{Union{Missing, Float64}}
x1::Union{Missing, Float64}
s::Float64
Body::Union{Missing, Float64}
1 ─ (s = 0.0)
│ (x1 = Base.getindex(x, 1))
│ %3 = Main.ismissing(x1)::Bool
└── goto #3 if not %3
2 ─ return s::Core.Const(0.0)
3 ─ %6 = (s::Core.Const(0.0) + x1)::Union{Missing, Float64}
└── return %6
Julia master:
julia> x = Union{Missing,Float64}[1.0]
1-element Vector{Union{Missing, Float64}}:
1.0
julia> function foo(x)
s = 0.0
x1 = x[1]
ismissing(x1) ? s : s + x1
end
foo (generic function with 1 method)
julia> foo(x)
1.0
julia> @code_warntype foo(x)
MethodInstance for foo(::Vector{Union{Missing, Float64}})
from foo(x) in Main at REPL[37]:1
Arguments
#self#::Core.Const(foo)
x::Vector{Union{Missing, Float64}}
Locals
x1::Union{Missing, Float64}
s::Float64
Body::Float64
1 ─ (s = 0.0)
│ (x1 = Base.getindex(x, 1))
│ %3 = Main.ismissing(x1)::Bool
└── goto #3 if not %3
2 ─ return s::Core.Const(0.0)
3 ─ %6 = (s::Core.Const(0.0) + x1::Float64)::Float64
└── return %6
It is easy to end up with type unstable code by checking for missing with ismissing instead of ===, unless you’re on Julia >=1.7-DEV.
(Note the Body::Union{Missing, Float64} on 1.6 vs Body::Float64 on master.)
So it sounds like there were two reasons: type instability, which was fixed, and “comparing pointers is faster than checking equality”
The implementation re-uses the same object for all Symbols with the same name, so comparison tends to be efficient (it can just compare pointers).
I normally consider interning an implementation detail that shouldn’t be relevant to users. Is that wrong here, or is it possible to automatically convert == into === for Symbols at compile time?
I’ll try to explain, but that’s likely to reveal more misunderstandings on my part.
I was trying to compare symbolic expressions produced by the Symbolics package.
== appears to be produce a new symbolic expression. === seems to check for some sort of structural equivalence (which is sometimes, but not always, useful).
Maple has evalb which has semantics something like, “No really check if they are equal, don’t just create a new expression capturing the postulate that they are equal”.
I’m momentarily under the impression that isequals is like Maple’s evalb.