Reputation: 133
I'm trying to define a struct NodeLocator
@with_kw mutable struct NodeLocator{T<:Int64}
length::T = 0
value::Vector{T} = [0]
maxtreelength::T = 0 end
And there are no problems with it.
However, I thought that my definition would be cleaner if I instead did
@with_kw mutable struct NodeLocator{T<:Union{Int64, Vector{Int64}}} # changed definition of T
length::T = 0
value::T = [0] # changed type declaration here
maxtreelength::T = 0 end
#since
julia> Vector{Int64} <: Union{Int64,Vector{Int64}}
true
But in this case I get an error when constructing objects of this type:
ERROR: MethodError: no method matching NodeLocator(::Int64, ::Vector{Int64}, ::Int64) Closest candidates are: NodeLocator(::T, !Matched::T, ::T) where T<:Union{Int64, Vector{Int64}} at C:\Users\ivica.julia\packages\Parameters\MK0O4\src\Parameters.jl:526
For context, their construction includes an elementwise addition of Int64
s to NodeLocator.value
, and I think that what's happening is that after the first step the type of that field gets locked at Int64
and cannot be changed to Vector{Int64}
.
How can I parameterise the type declaration of NodeLocator.value
without running into this error? - and does it make sense to do so? My main issue is computation time so performance is quite important for me.
Upvotes: 3
Views: 134
Reputation: 4370
It appears that you are using the @with_kw
macro from the Parameters package, so let's start by loading that package:
julia> using Parameters
Now, copying your definition
julia> @with_kw mutable struct NodeLocator{T<:Union{Int64, Vector{Int64}}}
length::T = 0
value::T = [0] # changed type declaration here
maxtreelength::T = 0 end
NodeLocator
we can see that this works fine if we construct a struct with either all ints or all vectors
julia> NodeLocator(0,0,0)
NodeLocator{Int64}
length: Int64 0
value: Int64 0
maxtreelength: Int64 0
julia> NodeLocator([0],[0],[0])
NodeLocator{Vector{Int64}}
length: Array{Int64}((1,)) [0]
value: Array{Int64}((1,)) [0]
maxtreelength: Array{Int64}((1,)) [0]
but fails for mixtures, including the default you have specified (effectively, (0, [0], 0)
)
julia> NodeLocator([0],[0],0)
ERROR: MethodError: no method matching NodeLocator(::Vector{Int64}, ::Vector{Int64}, ::Int64)
Closest candidates are:
NodeLocator(::T, ::T, ::T) where T<:Union{Int64, Vector{Int64}} at ~/.julia/packages/Parameters/MK0O4/src/Parameters.jl:526
Stacktrace:
[1] top-level scope
@ REPL[38]:1
julia> NodeLocator() # use defaults
ERROR: MethodError: no method matching NodeLocator(::Int64, ::Vector{Int64}, ::Int64)
Closest candidates are:
NodeLocator(::T, ::T, ::T) where T<:Union{Int64, Vector{Int64}} at ~/.julia/packages/Parameters/MK0O4/src/Parameters.jl:526
Stacktrace:
[1] NodeLocator(; length::Int64, value::Vector{Int64}, maxtreelength::Int64)
@ Main ~/.julia/packages/Parameters/MK0O4/src/Parameters.jl:545
[2] NodeLocator()
@ Main ~/.julia/packages/Parameters/MK0O4/src/Parameters.jl:545
[3] top-level scope
@ REPL[39]:1
This is because as written, you have forced each field of the struct to be the same type T
which is in turn a subtype of the union.
If you really want each type to be allowed to be a Union
, you could instead write something along the lines of:
julia> @with_kw mutable struct NodeLocator{T<:Int64}
length::Union{T,Vector{T}} = 0
value::Union{T,Vector{T}} = [0] # changed type declaration here
maxtreelength::Union{T,Vector{T}} = 0 end
julia> NodeLocator() # use defaults
NodeLocator{Int64}
length: Int64 0
value: Array{Int64}((1,)) [0]
maxtreelength: Int64 0
However, unless you really need the flexibility for all three fields to be unions, your original definition is actually much preferable. Adding Union
s where you don't need them can only slow you down.
One thing that would make the code cleaner and faster would be if you could use a non-mutable struct. This is often possible even when you might not realize it at first, since an Array
within a non-mutable struct
is still itself mutable, so you can swap out elements within that array without needing the whole struct to be mutable. But there are certainly cases where you do indeed need the mutability -- and if you do need that, go for it.
Finally, I should note that there is not necessarily much point in constraining the numeric type to be T<:Int64
unless that is really the only numeric type you'll ever need. You could easily make your code more general by instead writing something like T<:Integer
or T<:Number
, which should not generally have any adverse performance costs.
Upvotes: 5