Creating a function that only takes specific units

I’m new to Julia and currently trying to use Unitful.jl to create a function that only accepts arguments of a particular type of unit dimension, for example only length. Ideally, the type of the argument should be an array of AbstractFloat (or subtype) with any number of dimensions, and the only restriction is that the units should always be of a certain kind. Without this last restriction, I can already do it like this:

using Unitful

function test(a::Array{<:Quantity{<:AbstractFloat}})
    # something inside function
end

But I now would like to restrict the type of a to be of a unit of with a dimension of length (e.g. metres, km, nm, etc.). The following seems to work:

function test(a::typeof([1.0]u"m"))
    # something inside function
end

But this has several issues: it is now limited to 1D arrays, Float64, and it only takes units in metres (not km, or nm). Is there a way to write a type definition that will take an array of AbstractFloat, any dimension, and units of length?

I tried to build on the units of Unitful quantities, e.g:

julia> typeof(1.0u"m")
Quantity{Float64,𝐋,Unitful.FreeUnits{(m,),𝐋,nothing}}

Trying to copy paste and modifying that definition doesn’t work, e.g. I get an error ERROR: UndefVarError: m not defined. I get the feeling that there is probably a very simple way to get around this, but I couldn’t find it.

is an unregistered package that defines a bunch of convenience types: https://github.com/HolyLab/UnitAliases.jl/blob/master/src/UnitAliases.jl. If you find it useful perhaps we should integrate it into Unitful.

1 Like

Are you wanting to force a DimensionError to happen earlier than it would naturally? It feels like you’re trying to ask the library to do static checking when it is really designed to do it dynamically.

But if that’s what you need, perhaps a broadcast addition with 0u"m" would suffice?

Thank you. I found it quite convenient, and looking inside UnitAliases.jl I also found what I was missing: a way to access that caligraphic 𝐋 that I could not copy paste. So now I can define the type for the argument in this way, and works as intended:

length = typeof(1.0u"m").parameters[2]

function test(a::Array{<:Quantity{<:AbstractFloat, length}})
    # something inside function
end

This would also work with HasLengthUnits from UnitAliases.jl (e.g. a::Array{<:HasLengthUnits{<:AbstractFloat}), but I don’t have enough experience to tell if one is better than the other.

Yes. My goal was to force an error when the function is called, not an error that is called from inside the function. Maybe that is not a good thing. From the point of view of code clarity, I though it would be better if the function definition would be explicit on the type of units it expects for each argument. Perhaps it is also easier to hunt for bugs when the error happens just as the function is called, to avoid having to dig deeper inside the function (e.g. if the function passes the argument into another function, it will be one less hop to find the source of the problem).

It seems to me that a broadcast addition would impact performance, if the input is a large array.

I’m an astronomer and quite used to using astropy.units. Trying to learn how to use Unitful, it is possible that my way of looking into the problem is heavily biased by my experience with astropy.

What about

using Unitful

function test(a::Array{<:Unitful.Length})
    # do something
end

Thanks, that is really compact and what I was looking for! But how would that work for composite unit types, e.g. length / time? A naive approach of Unitful.Length / Unitful.Time does not work.

It works using Quantity{<:AbstractFloat, unit_type} where unit_type is one of e.g.:

  • typeof(1.0u"m").parameters[2] / typeof(1.0u"s").parameters[2]
  • typeof(1.0u"m/s").parameters[2]

So this is fine for my purposes, although not as clean.

Well for that particular example you could use

<:Unitful.Velocity

Look here for more dimensions.

2 Likes

That depends somewhat on your user. If you restrict the input types like you are doing, the user will get a MethodError if the units they are supplying are incorrect. If you instead check the units inside the function, you can provide a customized error message, like “this function requires the input arguments to have a length unit”. For experienced julia programmers, the former MethodError contains exactly this information, but for beginners it might be hard to parse.

Both errors will occur no runtime cost if implemented correctly.

1 Like