I am using this approach in a couple of projects:
julia> import StaticArrays: FieldVector, Size, similar_type
julia> struct Direction{T<:AbstractFloat} <: FieldVector{3, T}
x::T
y::T
z::T
end
julia> similar_type(::Type{<:Direction}, ::Type{T}, s::Size{(3,)}) where {T} = Direction{T}
similar_type (generic function with 21 methods)
julia> Direction(1.0, 0, 0)
3-element Direction{Float64} with indices SOneTo(3):
1.0
0.0
0.0
julia> Direction(1.0, 0, 0) / 2
3-element Direction{Float64} with indices SOneTo(3):
0.5
0.0
0.0
where I want to enforce that the type is a subtype of AbstractFloat
. Everything is fine so far, the type is parametric and the parameter type can be propagated to other types which uses this type as field type.
The problem I encounter with this approach is on the user side, namely the error message which is confusing:
julia> Direction(1, 0, 0)
ERROR: ArgumentError: cannot construct a value of type Union{} for return result
Stacktrace:
[1] (::Core.TypeofBottom)(a::Tuple{Int64, Int64, Int64})
@ Core ./boot.jl:318
[2] Direction(::Int64, ::Vararg{Int64})
@ StaticArrays ~/.julia/packages/StaticArrays/2dZx4/src/convert.jl:173
[3] top-level scope
@ REPL[11]:1
I first thought I can easily solve this with a generic method like:
julia> Direction(_, _, _) = throw(ArgumentError("All elements must be convertible to an AbstractFloat"))
Direction
julia> Direction(1, 0, 0)
ERROR: ArgumentError: All elements must be convertible to an AbstractFloat
Stacktrace:
[1] Direction(::Int64, ::Int64, ::Int64)
@ Main ./REPL[19]:1
[2] top-level scope
@ REPL[20]:1
julia> Direction(1.0, 0, 0)
ERROR: ArgumentError: All elements must be convertible to an AbstractFloat
Stacktrace:
[1] Direction(::Float64, ::Int64, ::Int64)
@ Main ./REPL[19]:1
[2] top-level scope
@ REPL[21]:1
but as seen above, it ignores the (in my view) more specific method in this case.
I also tried to replace the inner constructor and check the types there, which almost works, but as seen below, now the parametric constructor is gone So when I use already existing objects, the “type propagation” fails
julia> struct Direction{T<:AbstractFloat} <: FieldVector{3, T}
x::T
y::T
z::T
function Direction(x, y, z)
T = promote_type(typeof(x), typeof(y), typeof(z))
if !(T <: AbstractFloat)
throw(ArgumentError("All elements must be convertible to an AbstractFloat"))
end
new{T}(x, y, z)
end
end
julia> similar_type(::Type{<:Direction}, ::Type{T}, s::Size{(3,)}) where {T} = Direction{T}
similar_type (generic function with 21 methods)
julia> Direction(1, 0, 0)
ERROR: ArgumentError: All elements must be convertible to an AbstractFloat
Stacktrace:
[1] Direction(x::Int64, y::Int64, z::Int64)
@ Main ./REPL[2]:8
[2] top-level scope
@ REPL[3]:1
julia> Direction(1.0, 0, 0)
3-element Direction{Float64} with indices SOneTo(3):
1.0
0.0
0.0
julia> Direction(1.0, 0, 0) / 2
ERROR: The constructor for Direction{Float64}(::Float64, ::Float64, ::Float64) is missing!
Stacktrace:
[1] error(s::String)
@ Base ./error.jl:35
[2] _missing_fa_constructor(FA::Any, AT::Any)
@ StaticArrays ~/.julia/packages/StaticArrays/2dZx4/src/FieldArray.jl:13
[3] construct_type(::Type{Direction{Float64}}, x::StaticArrays.Args{Tuple{Float64, Float64, Float64}})
@ StaticArrays ~/.julia/packages/StaticArrays/2dZx4/src/FieldArray.jl:8
[4] StaticArray
@ ~/.julia/packages/StaticArrays/2dZx4/src/convert.jl:173 [inlined]
[5] FieldArray
@ ~/.julia/packages/StaticArrays/2dZx4/src/FieldArray.jl:2 [inlined]
[6] macro expansion
@ ~/.julia/packages/StaticArrays/2dZx4/src/mapreduce.jl:78 [inlined]
[7] _map
@ ~/.julia/packages/StaticArrays/2dZx4/src/mapreduce.jl:42 [inlined]
[8] map
@ ~/.julia/packages/StaticArrays/2dZx4/src/mapreduce.jl:33 [inlined]
[9] /(a::Direction{Float64}, b::Int64)
@ StaticArrays ~/.julia/packages/StaticArrays/2dZx4/src/linalg.jl:24
[10] top-level scope
@ REPL[5]:1
I am not sure how to solve this in an elegant way and I am currently stuck. I read through a couple of StaticArray topics but none of my endeavours were successful.
Maybe I am overcomplicating things and cat’t see the forest for the trees