Cannot define parametric type with parametric field

I am trying to define a type that support multiple array backends but the compiler complains:

struct A{R} # TypeError: in Type{...} expression, expected UnionAll, got TypeVar
    mat::R{Int,2}
    vec::R{Int,1}
end

In my mind R can be one of Array, BitArray, CuArray, SharedArray, etc.

One alternatives I can think of

struct A{Tmat<:AbstractArray{Int,2}, Tvec<:AbstractArray{Int,1}}
    mat::Tmat
    vec::Tvec
end

But it is much more verbose & hooked up to AbstractArray & lost the constraint that mat and vec share the same backend.

I am wondering how do people cope with this. Thoughts?

Using eg the solution you did above. AFAIK you cannot parametrize type names, only type parameters, which makes sense if you think about it.

If you definitely want to enforce that eg both mat and vec are Array, use an inner constructor. But IMO it should not be necessary in most cases I can think of, there is no reason not to keep your code fully generic. Recently I got into the habit of even leaving out the supertypes, and just relying on duck typing (“if it breaks, you will know”).

1 Like

For a lot more discussion on this kind of thing, see https://github.com/JuliaLang/julia/issues/18466

1 Like

Yes. But since I am sure my code will break some day, I would like to be able to use precautions here and there, rather than going full generic.

Exactly the discussion I am expecting to see! Thank you very much!

IMO the protection from overspecifying types is partly an illusion (even though I also use it). It just makes your code somewhat brittle, as subtyping does not allow something to conform to two disjoint interfaces — eg something can be an <: AbstractArray and something else at the same time.

Also, duck typing (allowing things to fail because methods are undefined) will lead to an error anyway.

Just to make this a bit more concrete — you can’t do this because type parameters don’t have a generic meaning. The first type parameter is not always the element type. The second type parameter is not always the dimensionality. A great example is BitArray: it only has one parameter and thus could not be meaningfully substituted in for R in your example. Conversely matrices like SparseMatrixCSC and Diagonal don’t have a dimensionality parameter since they are by definition 2-dimensional.

The way to enforce a constraint like this is just through the constructor.

2 Likes

Yes I agree about the “illusion ” part. Nonetheless I sometimes still want to have something like “enforcement of encapsulation” or “gerrenteed correctness/throw ”. The Julia language itself enforced so little that I often feel a lack of discipline. I am looking in established projects for ideas but has yet to distill enough essences to form some doctrines like one would find in any book about c++ design patterns.

You can enforce this constraint. It’s just done with the constructor instead of the struct layout.

1 Like