String macro to simplify type declarations with units

I am trying to simplify the declaration of struct variables that have units with a string macro. This works:

using Unitful

macro Float64_str(str)
    return :( typeof(Float64(1.0)*@u_str($str)) )
end

mutable struct Data1
    length::Float64"m"
end

d1 = Data1(2.0u"mm")
@show d1.length

I do not manage to get a similar approach for a parameterized type.

The goal is to implement this code fragement:

using Unitful

mutable struct Data2{Float}
    length::Quantity{Float, dimension(u"m"), typeof(u"m")} 
end

d2 = Data2{Float64}(2.0u"mm")
@show d2.length

The above code fragement works. I would like to get it in a more readable form by using again a string macro:

using Unitful

macro Float_str(str)
    return :( Quantity{Float, dimension(@u_str($str)), typeof(@u_str($str))} )
end

mutable struct Data3{Float}
    length::Float"m" 
end

d3 = Data3{Float64}(2.0u"mm")
@show d3.length

However, this code fragement gives an error:

UndefVarError: Float not defined

Help is appreciated.

String macro is likely the wrong approach. You should simply use a function like f(a, b) = Quantity{a, dimension(b), typeof(b)}.

As for why the macro is not working, you are assuming the name of the type parameter (which is REALLY bad for readability) and if you really want to do that, you have to pretend it is user input by escaping the Float symbol.

Besides @yuyichao’s points, there’s a typo in your second string macro.

macro Float_str(str)
    return :( Quantity{Float, dimension(@u_str($str)), typeof(@u_str($str))} )
end

should be

macro Float_str(str)
    return :( Quantity{Float64, dimension(@u_str($str)), typeof(@u_str($str))} )
end

String macro is likely the wrong approach. You should simply use a function like f(a, b) = Quantity{a, dimension(b), typeof(b)} .

Thanks. This proposal works and is a more compact representation:

using Unitful

quantity(a,b) = Quantity{a, dimension(b), typeof(b)} 

mutable struct Data4{Float}
    length::quantity(Float,u"m")
end

d4 = Data4{Float64}(2.0u"mm")
@show d4.length

As for why the macro is not working, you are assuming the name of the type parameter (which is REALLY bad for readability) and if you really want to do that, you have to pretend it is user input by escaping the Float symbol.

I did not get this suggestion to work. Maybe I misunderstood you (I used esc(Float) instead of Float):

using Unitful

macro Float_str(str)
    return :( Quantity{esc(Float), dimension(@u_str($str)), typeof(@u_str($str))} )
end

mutable struct Data3{Float}
    length::Float"m" 
end

d3 = Data3{Float64}(2.0u"mm")
@show d3.length

The above code fragement gives the same error.

(Your first proposal is clearer from a computer science point of view. If there are many declarations with units, the form length::Float"m" is nicer to read as length::quantity(Float,u"m"))

Besides @yuyichao’s points, there’s a typo in your second string macro. […]

No, this is not a typo: The goal is to use the type defined as parameter in the struct struct Data3{Float}, so using Float in the macro and not a concrete type, such as Float64.

Apologies, yes I had a silly moment there.

He means you should interpolate esc(:Float) into the expression. e.g.

julia> using Unitful

julia> macro Float_str(str)
           :(Quantity{$(esc(:Float)), dimension(@u_str($str)), typeof(@u_str($str))})
       end
@Float_str (macro with 1 method)

julia> mutable struct Data3{Float}
           length::Float"m"
       end

julia> d3 = Data3{Float64}(2.0u"mm")
Data3{Float64}(0.002 m)

But yeah, prt of why I got confused is that this macro is very specialized, it’s written assuming that the user has defined a variable Float outside of the macro, which is usually a bad idea.

1 Like

It’s nicer for readability (and more idiomatic) to write this as

mutable struct Data4{T <: AbstractFloat}
    length::quantity(T, u"m")
end

if you intend to limit the type to floats. And if you don’t intend to have that limitation, to simply leave T as untyped.

2 Likes

I said escaping the Float symbol

The symbol is represented as a literal Float currently and you need to replace it with the esc of the symbol (i.e. esc(:Float)). To get it in the expression, use $(...) to splice that in, i.e. replace Float with $(esc(:Float)).


edit: i.e.

1 Like

Thanks very, very much for all your suggestions. My questions are resolved. Let me summarize:

The Julia idiomatic way is

using Unitful

Quantity(a,b) = Quantity{a, dimension(b), typeof(b)} 

mutable struct Data4{Float <: AbstractFloat}
    length::Quantity(Float,u"m")
end

d4 = Data4{Float64}(2.0u"mm")
@show d4.length

Nicer to read, but hard to understand the implementation behind it with the (bad) assumption that a type Float is defined outside of the macro (so the macro only works under this assumption):

using Unitful 

macro Float_str(str)
    return :( Quantity{$(esc(:Float)), dimension(@u_str($str)), typeof(@u_str($str))} )
end

mutable struct Data3{Float <: AbstractFloat}
    length::Float"m"  
end

d3 = Data3{Float64}(2.0u"mm")
@show d3.length

Yes that’s correct. The second approach is also only bead because you want to make the macro name match the type parameter name. Making it @quantity(Float, "m") or quantity"Float,m" would not have this issue. (though for the second version you’d have to do some parsing with the string macro, which is basically true for all string macros…)

1 Like

For your information: I opened an issue in Unitful.jl and asked to introduce the function quantity(FloatType, unit) into Unitful.

1 Like