Tricky "too many parameters for type" error

Dear Julia community,

I get a “too many parameters for type” error that I cannot really understand.

Luckily, I have been able (after some effort) to isolate the issue to a very small reproducer:

module AAA

abstract type IterCellValue{R} end
abstract type Map{S,M,T,N} end
const IterCellMap{S,M,T,N,R} = IterCellValue{R} where R <:Map{S,M,T,N}
const Foo{S,T,N,R} = IterCellMap{S,1,T,N,R}

struct Bar{S,T,N,R} <: Foo{S,T,N,R} end # error here, help please!!

end # module

I would say that Foo has 4 type parameters. What I am missing?

Thanks in advance for your help!

============= EDIT ==============

I found myself the solution. Replace the first const declaration by an abstract type declaration. Namely:

module AAA

abstract type IterCellValue{R} end
abstract type Map{S,M,T,N} end
abstract type IterCellMap{S,M,T,N,R<:Map{S,M,T,N}} <: IterCellValue{R} end 
const Foo{S,T,N,R} = IterCellMap{S,1,T,N,R}

struct Bar{S,T,N,R} <: Foo{S,T,N,R} end # works!

end # module

However, I would like to know if it is the expected behavior the error I got in the first version.

Thanks for helping!

struct Bar{S,T,N,R} <: IterCellMap{S,1,T,N,R} end does not throw any errors. Does that help?

In this case Foo (and IterCellMap) only have one type parameter. When you put the line

const IterCellMap{S,M,T,N,R} = IterCellValue{R} where R <:Map{S,M,T,N}

Julia interprets this as

const IterCellMap = IterCellValue{R} where R<:Map{S,M,T,N} where N where T where M where S

i.e., IterCellMap is just the same as IterCellValue with a single parameter R. And likewise for Foo

3 Likes

Thanks for the anwser. Makes sense.

What surprises me is that I was able to go quite far with this wrong definition in my code until i found the error :slight_smile:

1 Like

I have been trying to better understand the problem. I have figured out an explanation, but I would be very grateful if someone can confirm whether it is correct.

abstract type Foo{A} end
const Bar = Foo{A} where A<:AbstractArray{T,N} where N where T
const BarVec = Bar{T,1,A} where A where T
struct MyBar{T} <: BarVec{T,Vector{T}} end # error: too many parameters for type

The problem is with line

const BarVec = Bar{T,1,A} where A where T

My explanation is that this line gets converted into

const BarVec = (Foo{A} where A<:AbstractArray{T,N} where N where T) where A where T

then into

# The info about A<:AbstractArray{T,N} is lost
const BarVec = (Foo{A} where A where N where T) where A where T

and finally into

const BarVec = Foo{A} where A

since Foo does not depend on T nor N

Is this interpretation correct?

why the restriction A<:AbstractArray{T,N} is lost in this process?

Thanks again for the help!
Francesc

Not quite correct; the info about A <: AbstractArray is not lost. Consider the following

julia> struct Foo{A} ; a::A ; end

julia> const Bar = Foo{A} where A <: AbstractArray  # note the where N where T is unnecessary
Foo{A} where A<:AbstractArray

julia> Bar{typeof([1])}([1])
Foo{Array{Int64,1}}([1])

julia> Bar{typeof(1)}(1)  # not an AbstractArray
ERROR: TypeError: in Type, in A, expected A<:AbstractArray, got Type{Int64}
Stacktrace:
 [1] top-level scope at none:0

julia> Foo{typeof(1)}(1)  # directly with Foo (doesn't have the subtyping relation imposed)
Foo{Int64}(1)

Note that AbstractArray{T,N} where N where T is equivalent to AbstractArray on its own.

1 Like

Yes, sure. But he info about A <: AbstractArray is lost in BarVec, right? This is what I meant.

No, the problem is that your BarVec line doesn’t make sense since Bar only has a single type parameter A (though that A is restricted to be an AbstractArray).

I think you are trying to achieve the following

julia> const BarVec = Bar{A} where A <: AbstractArray{T, 1} where T
Foo{A} where A<:AbstractArray{T,1} where T

julia> BarVec{Int64}{Vector{Int64}}([1])
Foo{Array{Int64,1}}([1])

but note that the constructor becomes more annoying to specify (multiple sets of braces). Personally I wouldn’t enforce multi-layered type constraints like this but instead use a constructor to enforce it.

1 Like

Thanks for your answer. Just a question:

#1
const Bar = Foo{A} where A<:AbstractArray{T,N} where N where T

#2
const Bar = Foo{A} where A<:AbstractArray

#3
const Bar = Foo{A} where {A<:AbstractArray{T,N} where N where T}

#2 and #3 are quivalent, but not #1, right?

Yes, you are right, I had forgotten that subtlety. As such, try

julia> struct Foo{A} ; a::A ; end

julia> const Bar = Foo{A} where {T, N, A <: AbstractArray{T, N}}
Foo{A} where A<:AbstractArray{T,N} where N where T

julia> const BarVec = Bar{T, 1} where T
Foo{A} where A<:AbstractArray{T,1} where T

julia> a = [1,2,3]
3-element Array{Int64,1}:
 1
 2
 3

julia> BarVec{eltype(a),typeof(a)}(a)
Foo{Array{Int64,1}}([1, 2, 3])

OK, I’ve worked out what the problem is (sorry, I was getting slightly confused)

on this line the where A seems to remove the subtyping relationship; simply omitting the A at the end of the parameter list (and the corresponding where A) causes the subtyping relationship to be preserved. This is why my example (which leaves the explicit A out of definition of BarVec) works but yours doesn’t.

1 Like

Yes, this is what puzzles me.

Is this the expected behavior or it is a bug?

I have vague memories of seeing a GitHub issue that mentions this problem but I can’t find it now. I don’t think it’s the intended behaviour but I suspect that it isn’t a high priority one to fix (particularly as it can be worked around as per my example).

1 Like