Yes, if youâre building a container then itâs advisable to try to give each element the same type, if possible. DataFrames is a bit of an exception, as itâs designed to hold heterogeneous types (since thatâs so often necessary), but for most other container types you get major performance advantages if all elements can be of the same type. Here, that means both the Float64
and the u"cm"
.
However, for functions it generally (not always) makes more sense to be permissive about the inputs. Unitful poses special challenges because itâs such a sophisticated example of Juliaâs type system. As you noted, Quantity
has 3 type parameters: the element type (T
), the dimensions (D
), and the actual unit (U
). To show how you might extract these quantities, letâs do a quick demo. I should acknowledge that Unitful contains its own type-manipulation utilities, some of which are far more sophisticated than these, but the point here is to build from the ground up.
So first some type utilities (using Julia 0.6 syntax):
using Unitful
# Implement the "main" operations on the types themselves
geteltype(::Type{Quantity{T,D,U}}) where {T,D,U} = T
getdimensions(::Type{Quantity{T,D,U}}) where {T,D,U} = D
getunits(::Type{Quantity{T,D,U}}) where {T,D,U} = U
# For instances, we can leverage the methods defined on types
geteltype(x::Quantity) = geteltype(typeof(x))
getdimensions(x::Quantity) = getdimensions(typeof(x))
getunits(x::Quantity) = getunits(typeof(x))
Now letâs build on these to build a utility that might be handy for dispatch:
typeof_generalized(x::Quantity) = Quantity{<:Number, getdimensions(x), getunits(x)}
and then try it out:
julia> xfloat = 1.0u"cm"
1.0 cm
julia> xint = 1u"cm"
1 cm
julia> y = 1.0u"m"
1.0 m
julia> getdimensions(xfloat)
Unitful.Dimensions{(Unitful.Dimension{:Length}(1//1),)}
julia> getunits(xfloat)
Unitful.FreeUnits{(Unitful.Unit{:Meter,Unitful.Dimensions{(Unitful.Dimension{:Length}(1//1),)}}(-2, 1//1),),Unitful.Dimensions{(Unitful.Dimension{:Length}(1//1),)}}
julia> getunits(y)
Unitful.FreeUnits{(Unitful.Unit{:Meter,Unitful.Dimensions{(Unitful.Dimension{:Length}(1//1),)}}(0, 1//1),),Unitful.Dimensions{(Unitful.Dimension{:Length}(1//1),)}}
julia> foo(x::typeof_generalized(1.0u"cm")) = 1
foo (generic function with 1 method)
julia> foo(xfloat)
1
julia> foo(xint)
1
julia> foo(y)
ERROR: MethodError: no method matching foo(::Quantity{Float64, Dimensions:{đ}, Units:{m}})
Closest candidates are:
foo(::Unitful.Quantity{#s1,Unitful.Dimensions{(Unitful.Dimension{:Length}(1//1),)},Unitful.FreeUnits{(Unitful.Unit{:Meter,Unitful.Dimensions{(Unitful.Dimension{:Length}(1//1),)}}(-2, 1//1),),Unitful.Dimensions{(Unitful.Dimension{:Length}(1//1),)}}} where #s1<:Number) at REPL[16]:1
If you understand this, youâre now a master of Juliaâs type system .