Function property differs based on the position of the generic type

Let

julia> using LinearAlgebra

julia> id(X,T) = T(Matrix{eltype(X)}(I, size(X)...))
id (generic function with 1 method)

julia> A = complex.(randn(2,2),randn(2,2));

julia> typeof(transpose(id(A,Hermitian)))
Transpose{ComplexF64, Hermitian{ComplexF64, Matrix{ComplexF64}}}

Now, we define two functions as follows:

julia> f(A::Transpose{T,Hermitian{T,M}}) where {T,M <: StridedMatrix} = 1
f (generic function with 1 method)
julia> methods(f)
# 1 method for generic function "f" from Main:
 [1] f(A::Transpose{T, Hermitian{T, M}}) where {T, M<:(StridedMatrix{T} where T)}
     @ REPL[5]:1

julia> g(A::Transpose{T,Hermitian{T, <:StridedMatrix}}) where {T} = 1
g (generic function with 1 method)
julia> methods(g)
# 1 method for generic function "g" from Main:
 [1] g(A::Transpose{T, Hermitian{T, <:StridedMatrix{T} where T}}) where T
     @ REPL[7]:1

I would expect both functions to accept a variable of type transpose(id(A,Hermitian)). However, f works and g does not!

julia> f(transpose(id(A,Hermitian)))
1

julia> g(transpose(id(A,Hermitian)))
ERROR: MethodError: no method matching g(::Transpose{ComplexF64, Hermitian{ComplexF64, Matrix{ComplexF64}}})

Closest candidates are:
  g(::Transpose{T, Hermitian{T, <:StridedMatrix{T} where T}}) where T
   @ Main REPL[7]:1

Stacktrace:
 [1] top-level scope
   @ REPL[10]:1

Could someone please explain why this is the case? That is, why must M be explicitly declared here although the method signatures seem equivalent.

You need a <: before Hermitian:

g(A::Transpose{T,<:Hermitian{T, <:StridedMatrix}}) where {T} = 1

With Transpose{T, Hermitian{T, <:StridedMatrix}} where T (without the <:), it’s analogous to how Vector{Any} is a concrete type, and [1, 2, 3] isa Vector{Any} is false.

1 Like

Thank you for the explanation! Based on your response, I’m guessing that using M or <: again transforms it analogous to

julia> [1,2,3] isa Vector{Any}
false

julia> [1,2,3] isa Vector{<:Any}
true

julia> [1,2,3] isa Vector{T} where {T<:Any}
true

The mistake in my thinking was that I expected Transpose{T,Hermitian{T, <:StridedMatrix}} to be generic because of the <:StridedMatrix. But, I guess every level of a nested type needs <: to remain generic.