How can I do something like:
struct Foo{N}
arr1::Array{Float64, N}
arr2::Array{Float64, N+1}
end
i.e., I want arr2 to be always of 1 higher rank than arr1. How to do this?
How can I do something like:
struct Foo{N}
arr1::Array{Float64, N}
arr2::Array{Float64, N+1}
end
i.e., I want arr2 to be always of 1 higher rank than arr1. How to do this?
Maybe there’s a fancier way but this seems to work fine.
julia> struct Foo{A,B}
a::A
b::B
Foo{A,B}(a,b) where {A,B} = ndims(a)+1 == ndims(b) ? new{A,B}(a,b) : error("wrong dims")
end
julia> (Foo(a::A,b::B) where {A,B}) = Foo{A,B}(a,b)
Foo
julia> Foo([1], [1;;2])
Foo{Vector{Int64}, Matrix{Int64}}([1], [1 2])
julia> Foo([1], [1])
ERROR: wrong dims
It is impossible for the composite type to do parameter constraints other than <:
in the implicit where
clause, you need to enforce it in an inner constructor call like jar1 demonstrated. The reason is that generic functions like +
can depend on input types and can be changed interactively, so a constraint based on a generic method could also change interactively, but a composite type CANNOT change because of existing instances. That’s also why you need the “redundant” parameter B==A+1
; after a constraint change, the type system must distinguish previous Foo{1, 2}
instances from newer Foo{1, 42}
instances.
The inner constructor should be enough, it is unsafe (in other words, user beware) to bypass inner constructors with reinterpret
or @eval @inline outnew(T, args...) = $(Expr(:splatnew, :T, :args)) #:new for an unsplatted number of separate arguments
. I am uncertain if it is hypothetically possible to extend constraints to intrinsic and built-in functions, which I don’t think can change.
I would do the following.
julia> struct Foo{N,M}
arr1::Array{Float64, N}
arr2::Array{Float64, M}
Foo{N}(a,b) where N= new{N, N+1}(a,b)
end
julia> Foo{1}(rand(2), rand(2,3))
Foo{1, 2}([0.9165330134722103, 0.8133055136507046], [0.15120619990261142 0.8543356012671888 0.5239972992036701; 0.12074486400173157 0.6817097391005016 0.3327645622931258])
That’s even neater and closer matches the OP, and I’d suggest rounding it out with an outer constructor like jar1 did for convenience:
julia> Foo(a, b) = Foo{ndims(a)}(a, b)
Foo
julia> Foo(rand(2), rand(2,3))
Foo{1, 2}([0.7928217615317612, 0.27627055084273566], [0.8947120194686449 0.7069448391898014 0.628333742859724; 0.3193547716322238 0.7802926781126777 0.9817486584424423])
and incorporating a more descriptive error message in the inner Foo{N}
than a conversion error in new
. Note this approach consciously sacrifices a Foo{N, M}
constructor, so the output of typeof
can’t instantiate.
I like ConstructionBase.constructorof
for getting constructors instead of typeof
.