Consider this MWE:
using Unitful
# 1. Get the type of a quantity constructed from the Unitful macro
t1 = typeof(1u"m")
# 2. Manually construct a type with the same structure
t2 = Unitful.Quantity{Int64, Unitful.๐, Unitful.FreeUnits{(Unitful.m,), Unitful.๐, nothing}}
# 3. Compare
println("t1 == t2: ", t1 == t2) # false โ structurally different
println("t1 === t2: ", t1 === t2) # false โ different type objects in memory
This has come up before, though I canโt find the other thread at the moment. I think the type isnโt being printed faithfully. You can do:
t3 = Quantity{Int64, dimension(u"m"), typeof(u"m")}
@show t1 == t3 # true
@show t1 === t3 # also true
3 Likes
Thanks for the response; this is a very subtle issue that took a long time to track down. Since dispatch depends on the faithful comparison of types, what is a good strategy to prevent this problem?
Wow, thatโs very cursed. Itโs the difference between:
julia> Unitful.FreeUnits{(Unitful.Unit{:Meter, Unitful.๐}(0, 1//1),), Unitful.๐, nothing}()
m
julia> Unitful.Unit{:Meter, Unitful.๐}(0, 1//1)
m
Both of those things print as m
, but only one of them is the same as Unitful.m
.
julia> typeof(Unitful.m)
Unitful.FreeUnits{(m,), ๐, nothing}
1 Like
In the end I wanted to do something like this:
const DeviceUnits{T} = Union{
Quantity{T, dimension(u"m"), typeof(u"m")},
Quantity{T, dimension(u"cm"), typeof(u"cm")}} where T <: Number
and there was no easy way to construct the types. This works but the prior construction which used
Quantity{Int64, Unitful.๐, Unitful.FreeUnits{(Unitful.m,), Unitful.๐, nothing}}
did not.
I often find myself fighting the Julia type system.
Iโm not adept enough to understand the mechanism that enables this.
Itโs not about Juliaโs type system; itโs that Unitful is choosing to print two subtly different values as the same thing. And then it also defines a variable with the same name as what prints out.
It just becomes particularly confusing when you put these values into a type parameter. Hereโs whatโs effectively happening:
By default, Julia prints different values differently:
julia> struct A end
julia> struct B end
julia> A()
A()
julia> B()
B()
But you can override that:
julia> Base.show(io::IO, ::A) = print(io, "something")
julia> Base.show(io::IO, ::B) = print(io, "something")
julia> A()
something
julia> B()
something
And then it becomes even more confusing if that value ends up as a parameter of a typeโฆ or if you have another variable named something
.
julia> t1 = Val{A()}
Val{something}
julia> t2 = Val{B()}
Val{something}
julia> t1 == t2
false
8 Likes
aha, this makes dispatch with Unitful types particularly painful. I read in the docs that they recommend avoiding it. Thanks for the response!
I donโt know if this was missed in the discussion, but I frequently use dispatch on the supertypes like Unitful.Length based on the SI dimensions.
foo(x::Unitful.Length) = "Is a Unit of Length"
foo(x::Unitful.AbstractQuantity) = "Is NOT a Unit of Length"
julia> foo(1u"m")
"Is a Unit of Length"
julia> foo(1.0u"ft")
"Is a Unit of Length"
julia> foo(1u"kg")
"Is NOT a Unit of Length"
If not already defined, the @derived_dimension
macro can be used for a different combination of SI dimensions.
4 Likes
Adding to @Daniel_Berge 's answer, you can easily dispatch on more specific aliases:
const Len{T} = Quantity{T,u"๐"}
const Met{T} = Quantity{T,u"๐",typeof(m)}
const Deg{T} = Quantity{T,NoDims,typeof(ยฐ)}
const Rad{T} = Quantity{T,NoDims,typeof(rad)}
You donโt need to construct concrete Unitful.jl types for dispatch. And you can let Julia figure out the exact type in a field using a type parameter:
struct Foo{L<:Met}
len::L
end
5 Likes