Typed Units instead of parameterized (Unitful) or exponent-tracking (DynamicQuantities)?

Adding units to a package greatly obfuscates error messages, slowing development. If you share this annoyance I’d appreciate your thoughts on a package, ‘UnitTypes’, that simply treats units as types. The package would

  • define SI / common units as Julia types [::Meter, ::MilliMeter, …]
  • define an abstract type tree to relate units [Meter <: AbstractExtent] and facilitate conversion
  • define convert(),promote(), for each type…just as you normally do
  • provide a macro to introduce new types

I haven’t seen this done; the closest are comments about it being laborious to manually define the whole type tree. I have used Unitful.jl widely enough to be annoyed at it, and while DynamicQuantities is an improvement, it was motivated by an all-encompassing type, which is what I’d like to avoid.

Thanks

1 Like

What do you see as the downside of the “all-encompassing type” design of DynamicQuantities? I read their release pitch but haven’t had occasion to try it yet.

It’s somewhat unclear to me how derived units would work; i.e. if you have a value of type ::Meter and divide it by a value of type ::Second, is the result a unique type, like ::MeterperSecond? Would all such derived units require definition at time-of-writing, or would they be generated or something like that?

I often work with values that represent physical units and most of the time I don’t use unit packages because I find the gain in ease-of-use initially is offset by development being more complicated down the road. So in general I am interested in improvements to the unit ecosystem.

1 Like

In functions requiring a certain unit type; the second call should error but succeeds:

calculateDistance(a::typeof(1u"m")) = a*2
@show calculateDistance(1u"m")
@show calculateDistance(1u"s")

> calculateDistance(1 * u"m") = 2.0 m
> calculateDistance(1 * u"s") = 2.0 s

(I didn’t see how to properly assert a type in DynamicQuantities)

Whereas Unitful:

using Unitful
calculateDistance(a::Unitful.Length) = 3*a
@show calculateDistance(1u"ft")
@show calculateDistance(1u"s")

> calculateDistance(1 * u"ft") = 3 ft

> ERROR: MethodError: no method matching calculateDistance(::Unitful.Quantity{Int64, 𝐓 , Unitful.FreeUnits{(s,), 𝐓 , nothing}})

Closest candidates are:
  calculateDistance(::Union{Unitful.Quantity{T, 𝐋, U}, Unitful.Level{L, S, Unitful.Quantity{T, 𝐋, U}} where {L, S}} where {T, U})

correctly complains that seconds were given instead of Unitful.Length, but the error message requires knowing Unitful’s type design in order to locate the problem and the verbosity of the closest candidate is even worse.

Can Unitful types be simplified without other major changes to the package design? It’s a great battle-tested package, maybe Quantity can be implemented with just two simple parameters (underlying number type + unit)?

1 Like

And yes, I think written-out labels (::MeterPerSecond) are most easily implemented and straightforwardly understood, though I half recall a discussion in Unitful about providing a ‘display name’ to the error machinery, allowing ‘m/s’ to be printed instead.

That’s on my experiments list, to see whether a bunch of convert()s would allow clean type assertions and errors while relying on Unitful for conversions between units. Of course I’d really like for unit conversion errors to be readable and that may prohibit including Unitful.

another option could be to improve the show method for Unitful.Quantity types?, like what was done recently with named tuples: print NamedTuple types using the macro format by KristofferC · Pull Request #49117 · JuliaLang/julia · GitHub

I use units regularly in MathCad, rarely in Julia. From the user experience point of view, units are nicely handled there. I don’t know if it can be inspiration for a Julia package? I show a quick velocity calculation and then display the result with 3 different units. The default is SI with the option to use British (which I never do) and the other two are arbitrary units that I chose.

Sorry that I cannot help with the technical details of implementing this.

I put these ideas into UnitTypes; give it a try and let me know what you think. Though I’m using this internally, expect bugs and send along any design and implementation suggestions.