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 ![]()