Method matching an array with elements of a composite type with parametric types

Here is my composite type:

mutable struct Factor{T, N}
  vars::NTuple{N,Int64}
  vals::Array{T,N}
end

How can I define a method that matches arrays with elements of this type and that can access the parametric type T?

Here is my attempt:

function product(F::Array{Factor{T,N},1}) where {T,N}
  reduce(product, F; init = Factor{T}((), Array{T,0}(undef)))
end

But it fails:

A = Factor{Float64,1}((1,), [0.11; 0.89])
B = Factor{Float64,2}((2, 1), [0.59 0.22; 0.41 0.78])
C = product([A, B])

Error:

MethodError: no method matching product(::Array{Factor{Float64,N} where N,1})

The signature product(F::Array{Factor{T,N},1}) where {T,N} requires that N be a defined value for the whole (specialized) method. But in your case you want to allow different N values for different array elements, so you can write:

function product(F::Array{Factor{T,N} where N, 1}) where T
  # ... code that uses `T`
end

or equivalently, just leave N unspecified:

function product(F::Array{Factor{T}, 1}) where T
  # ... code that uses `T`
end

However, you need to change the body of the method to choose a specific N when you instantiate a Factor.

1 Like

Thank you very much (also for anticipating the next bug in my program)

1 Like

This will only work when the user has specifically created an Array{Factor{T}, 1}, that is, an array in which every different element can have a potentially different N in its Factor parameters. That’s almost certainly not what you want, because it will fail in some simple cases:

julia> product([A])
ERROR: MethodError: no method matching product(::Array{Factor{Float64,1},1})
You may have intended to import Base.product
Closest candidates are:
  product(::Array{Factor{T,N} where N,1}) where T at REPL[4]:1
Stacktrace:
 [1] top-level scope at REPL[5]:100: 

You almost certainly want:

function product(F::Array{<:Factor{T}, 1}) where {T}
  # code that uses `T`
end

which accepts any array whose element type is some subtype of Factor{T}.

Even better, unless you actually care about only accepting the built-in type Array and not any other array-like object, you probably want:

function product(F::AbstractArray{<:Factor{T}, 1}) where {T}

or, exactly equivalently:

function product(F::AbstractVector{<:Factor{T}}) where {T}

since AbstractVector{T} is exactly AbstractArray{T, 1}.

2 Likes

Or maybe even

function product(F::AbstractArray{<:Factor{T}}) where {T}

?

1 Like

Could you explain why

product(F::Array{Factor{T}, 1}) where T

fails in simple cases like the one you show?

You mention that:

This will only work when the user has specifically created an Array{Factor{T}, 1} , that is, an array in which every different element can have a potentially different N in its Factor parameters.

Isn’t

julia> product([A])

Covered by that statement?

In which other simple cases does it fail?

Check out What's with the invariant parametric types gotcha? - #3 by rdeits or Why? isa([(x,1),(y,1)], Array{Tuple{Stuff,Number},1}) = false - #5 by Shuhua or Struggling with signatures - #2 by Henrique_Becker

In particular, Array{Factor{T}, 1} and Array{Factor{T, 1}, 1} are totally different types, but they are both subtypes of Array{<:Factor{T}, 1}

2 Likes

What change would I need to make to

function product(F::Array{Factor{T,N} where N, 1}) where T
  # ... code that uses `T`
end

to be able to access N inside the body of the function?

You can’t… That would require that N is a defined value for the whole method, which is not the case.

Say the method is called like this:

A = Factor{Float64,1}((1,), [0.11; 0.89])
B = Factor{Float64,2}((2, 1), [0.59 0.22; 0.41 0.78])
C = product([A, B])

Here N=1 for A, and N=2 for B. So what should N be in the body of product?

1 Like