Type of array containing units from Unitful

Hello! I am struggling a bit with types coming from Unitful in arrays. Here is a simple MWE:

using Unitful

array_unit = [1.0, 3, 4]u"yr"

If I look at the type of array_unit, I obtain this:

typeof(array_unit)
Vector{Quantity{Float64, 𝐓, FreeUnits{(yr,), 𝐓, nothing}}} (alias for Array{Quantity{Float64, 𝐓, Unitful.FreeUnits{(yr,), 𝐓, nothing}}, 1})

So why does this not work?

array_unit::Array{Quantity{Float64, Unitful.𝐓, Unitful.FreeUnits{(Unitful.yr,), Unitful.𝐓, nothing}}, 1}

This throws:

ERROR: TypeError: in typeassert, expected Vector{Quantity{Float64, 𝐓, Unitful.FreeUnits{(yr,), 𝐓, nothing}}}, got a value of type Vector{Quantity{Float64, 𝐓, Unitful.FreeUnits{(yr,), 𝐓, nothing}}}
Stacktrace:
 [1] top-level scope
   @ REPL[13]:1

which seems to be the same thing to me…

I am still a bit new to types so maybe I am missing something fondamental here.

Cheers,

2 Likes

The following works:

array_unit::Array{Quantity{Float64, Unitful.𝐓, typeof(Unitful.yr)}, 1}

The problem is that Unitful customizes how some of its types are printed to make them shorter. So the type that is printed is not the actual type.

In this specific case, Unitful.yr is not the same object as the one represented by yr in Unitful.FreeUnits{(yr,), 𝐓, nothing}. You can see that if you look at the type of Unitful.yr:

julia> typeof(Unitful.yr)
Unitful.FreeUnits{(yr,), 𝐓, nothing}

Unitful.yr is already a Unitful.FreeUnits object, while the object that yr refers to in the above type signature is a Unitful.Unit object (specifically, it is the object Unit{:Year, 𝐓}(0, 1//1) with type Unit{:Year, 𝐓}).

I think the custom printing of Unit (making it look as if it were a FreeUnits) is really confusing and we should get rid of it, but the type signatures would be a lot longer if we removed customized type printing. Maybe, since we already customize type printing, we should print Quantity types in the following way:

Quantity{Float64, 𝐓, typeof(yr)}

I think this would be an improvement to the printing of Quantity types, but doesn’t solve the problem of how Units types are displayed.

2 Likes

Hmm… perhaps a way to improve pretty printing of Unitful types while also achieving copy-into-REPL-ability is to draw inspiration from @NamedTuple in Julia β‰₯v1.5.

Suppose Unitful exported a macro so that, for example,

@Quantity{Float64, 𝐓, yr} === Quantity{Float64, Unitful.𝐓, typeof(Unitful.yr)}

Then types could be pretty printed as calls to this macro.

I really like that idea.

Thx for the explanation, it works indeed. But I still struggle to extend this to what I want to do.

Basically, let’s say I want to create a function that takes an array with Units of Time and update an array without units from the values in s:

using Unitful

array_unit = [1.0, 3, 4]u"yr"
array_without_unit = similar(array_unit, Float64)

function remove_unit!(array_without_unit::Vector{Float64}, array_unit::Array{Quantity{Float64, Unitful.𝐓, typeof(Unitful.yr)}, 1})
    array_without_unit .= ustrip.(u"s", array_unit)
end

remove_unit!(array_without_unit, array_unit)

That works nicely now. But only if the input has yr and is composed of Float64. How can I generalise that for array containing Real and Time units? I have tried a couple of things, but my understanding is not enough to do it.

First, I’ve replaced Float64 by <:Real like I would do for an array. This doesn’t work:

function remove_unit2!(array_without_unit::Vector{Float64}, array_unit::Array{Quantity{<:Real, Unitful.𝐓, typeof(Unitful.yr)}, 1})
    array_without_unit .= ustrip.(u"s", array_unit)
end

remove_unit2!(array_without_unit, array_unit)

And for the units par, I’ve tried randomly a few things without much success. Is there an easier way?

You don’t actually need any type annotations for this to work, in case that’s not clear.

function remove_unit!(array_without_unit, array_unit)
    array_without_unit .= ustrip.(u"s", array_unit)
end

If you’re wanting to dispatch on the quantity dimension, then given the complexity of Unitful types, perhaps it would be better to use a dictionary to associate dimensions with preferred units …

Edit
See Highlighted features Β· Unitful.jl for how to dispatch on dimensions. It looks like you can do

function remove_unit!(array_without_unit, array_unit::Unitful.Time)
    array_without_unit .= ustrip.(u"s", array_unit)
end

You need to add <: to all types in containers:
Array{<:Quantity{<:Real, ...}}

1 Like

This doesn’t work because Quantity{<:Real, Unitful.𝐓, typeof(Unitful.yr)} is an abstract type, but the array you are passing has is a concrete element type (which is a subtype of the above). You need to add another <: like this:

array_unit::Array{<:Quantity{<:Real, Unitful.𝐓, typeof(Unitful.yr)}, 1}

Yes, there is the Unitful.Time type alias, which matches all quantities with time units:

array_unit::Array{<:Unitful.Time{<:Real}, 1}
1 Like

Thx a lot for all the responses! I was aware of the Unitful.Time alias, but could not make it work for arrays… @sostock got me covered :+1:

I feel like I still need a bit of time to understand all the implications of <: and types but I will get there I guess!

One nice trick to know about when dealing with Unitful shenanigans is typeof(1u"yr").