Dispatch on specific Unitful unit

Continuing the discussion from Unitful dispatch:

I want to dispatch specifically on u"inch". What am I missing?
In this MWE, I want mm(1), mm(1u"inch"), and mm(1.0u"inch") to all return 25.4 mm.

using Unitful
InchType = Quantity{
    <:Any,
    Unitful.𝐋,
    Unitful.FreeUnits{(Unitful.inch,), Unitful.𝐋, nothing},
}
function mm(x::Real)
    x * u"inch" |> u"mm" |> float
end
function mm(x::InchType)
    x |> u"mm" |> float
end
julia> x = 1u"inch"
1 inch

julia> XType = typeof(x)
Quantity{Int64, 𝐋 , Unitful.FreeUnits{(inch,), 𝐋 , nothing}}

julia> InchType
Quantity{<:Any, 𝐋 , Unitful.FreeUnits{(inch,), 𝐋 , nothing}}

julia> XType <: InchType
false

julia> mm(x)
ERROR: MethodError: no method matching mm(::Quantity{Int64, 𝐋, Unitful.FreeUnits{(inch,), 𝐋,
 nothing}})
The function `mm` exists, but no method is defined for this combination of argument types.

Closest candidates are:
  mm(::Quantity{<:Any, 𝐋, Unitful.FreeUnits{(inch,), 𝐋, nothing}}  , ::Quantity{<:Any, 𝐋, Uni
tful.FreeUnits{(inch,), 𝐋, nothing}} )
   @ Main C:\Users\nboyer.AIP\.julia\config\startup.jl:67
  mm(::Real, ::Real)
   @ Main C:\Users\nboyer.AIP\.julia\config\startup.jl:64
  mm(::Tuple{Quantity{<:Any, 𝐋, Unitful.FreeUnits{(inch,), 𝐋, nothing}}, Quantity{<:Any, 𝐋,
 Unitful.FreeUnits{(inch,), 𝐋, nothing}}} )
   @ Main C:\Users\nboyer.AIP\.julia\config\startup.jl:61
  ...

Stacktrace:
 [1] top-level scope
   @ REPL[1]:1

I’ve had this desire in the past but can’t find my resolution; since then I wrote UnitTypes to help with this sort of thing. The package works by providing macros that allow defining of new unit types and conversions between them. A parallel package, ExchangeUnitful, can provide converts to/from Unitful.
N.B: I’m rewriting the macros to make the expression more natural.

1 Like

One possible solution:

using Unitful

# type to dispatch on length quantities
const Length{T} = Quantity{T,u"𝐋"}

# requested behavior
mm(x::Number) = mm(x * u"inch")
mm(x::Length) = float(uconvert(u"mm", x))
mm(x::Quantity) = throw(ArgumentError("invalid units"))

# examples of usage
mm(1) # 25.4 mm
mm(1u"inch") # 25.4 mm
mm(1u"hr") # invalid units
2 Likes

I’m mostly just confused why

julia> x = 1u"inch"
1 inch

julia> XType = typeof(x)
Quantity{Int64, 𝐋, Unitful.FreeUnits{(inch,), 𝐋, nothing}}

julia> LengthType = Quantity{<:Any, Unitful.𝐋}
Quantity{<:Any, 𝐋}

julia> XType <: LengthType
true

but

julia> InchType = Quantity{
           <:Any,
           Unitful.𝐋,
           Unitful.FreeUnits{(Unitful.inch,), Unitful.𝐋, nothing},
       }
Quantity{<:Any, 𝐋, Unitful.FreeUnits{(inch,), 𝐋, nothing}}

julia> XType <: InchType
false

when I’ve copied everything else exactly.

Julia’s type parameters are invariant:

Another example:

julia> abstract type A{T₁,Tβ‚‚,T₃} end

julia> A{Int,Int,Int} <: A{Int,Int} <: A{Int} <: A
true

julia> A{Int,Int,Int} <: A{Number,Number,Number}
false

I found this confusing but it turns out to be due to how Unitful.jl prints units and what you really want is the type of a unit, demonstrated below:

julia> InchType = Quantity{
           <:Any,
           Unitful.𝐋,
           typeof(Unitful.inch),
       }
Quantity{<:Any, 𝐋, Unitful.FreeUnits{(inch,), 𝐋, nothing}}

julia> XType <: InchType
true

2 Likes

However, if you want to dispatch on the unit, it could also be more readable to treat the unit like a trait:

f(x::Number) = _f(unit(x), x)
_f(::Unitful.Units, x) = nothing
_f(::typeof(Unitful.inch), x) = something
1 Like