rafaqz / FieldMetadata.jl

Metadata for julia fields
Other
22 stars 4 forks source link

Add extra constructor when using @units #5

Open briochemc opened 4 years ago

briochemc commented 4 years ago

I was thinking about this toy example:

julia> using FieldMetadata, Unitful

julia> import FieldMetadata: @units, units

julia> @units struct Foo{T}
           a::T | u"m"
           b::T | u"s"
       end
units (generic function with 11 methods)

julia> foo = Foo(1.0, 2.0) # works
Foo{Float64}(1.0, 2.0)

julia> foo = Foo(1.0u"km", 2.0u"hr") # does not
ERROR: MethodError: no method matching Foo(::Quantity{Float64,𝐋,Unitful.FreeUnits{(km,),𝐋,nothing}}, ::Quantity{Float64,𝐓,Unitful.FreeUnits{(hr,),𝐓,nothing}})
Closest candidates are:
  Foo(::T, ::T) where T at REPL[3]:2
Stacktrace:
 [1] top-level scope at REPL[5]:1

Now I can add a specific constructor to deal with Unitful arguments:

julia> Foo(args::Quantity...) = Foo([ustrip(x |> units(Foo, f)) for (x,f) in zip(args, fieldnames(Foo))]...)
Foo

julia> foo = Foo(1.0u"km", 2.0u"hr") # now works
Foo{Float64}(1000.0, 7200.0)

I'm pretty sure my solution is not optimal but anyway, do you think it is possible to automatically generate that constructor (that takes in units and converts them) if @units is used?

rafaqz commented 4 years ago

The solution is to always use a separate type parameter for every field. Whihc also has other benefits.

briochemc commented 4 years ago

Mmm no I meant something different. Take a single field example:

julia> using FieldMetadata, Unitful

julia> import FieldMetadata: @units, units

julia> @units struct Foo{T}
           a::T | u"m"
       end
units (generic function with 11 methods)

julia> foo = Foo(1.0) # works as intended
Foo{Float64}(1.0)

julia> foo = Foo(1.0u"km") # should convert to 1000.0u"m", but does not...
Foo{Quantity{Float64,𝐋,Unitful.FreeUnits{(km,),𝐋,nothing}}}(1.0 km)

julia> Foo(args::Quantity...) = Foo([ustrip(x |> units(Foo, f)) for (x,f) in zip(args, fieldnames(Foo))]...)

julia> foo = Foo(1.0u"km") # now works as intended
Foo{Float64}(1000.0)
rafaqz commented 4 years ago

Ah ok the T in the first example confused me. That is an interesting problem. You could define T<:typeof(1.0u"m"). I guess FieldDefaults.jl could define a constructor but its getting pretty case specific... I don't actually use units, I just put them in the struct.