I have multiple structs (FooKey and BarKey), each of which has its own type parameter (FooType and BarType). Can I write the constructor for these structs in a parametrized manner? Here’s my attempt:
abstract type AllType end
abstract type FooType <: AllType end
abstract type BarType <: AllType end
abstract type AllKey{T <: AllType} end
struct FooKey{T <: FooType} <: AllKey{T}
x::Int
end
struct BarKey{T <: BarType} <: AllKey{T}
x::Int
end
M(::Type{T}, x::Int) where {T <: AllType, M <: AllKey{T}} = M{T}(x)
FooKey(FooType, 1) # should return FooKey{FooType}(1)
BarKey(BarType, 2) # should return BarKey{BarType}(2)
But if I do that, Julia treats the function name M as a different M than the type parameter.
M is a type, so it should only be inferred in the type domain. However, Julia disallows directly dispatching methods on a type, but only on type parameters. For example:
(::M)(args...) where {M} = (M, args) # Illegal
(::Val{M})(args...) where {M} = (M, args) # Legal
Based on your code, I think this might be the solution close to your needs:
julia> struct KeyHolder{K<:AllKey}
KeyHolder(::Type{K}) where {K<:AllKey} = new{K}()
end
julia> function (::KeyHolder{K})(::Type{T}, x::Int) where {T<:AllType, K<:AllKey}
sym = nameof(K)
constructor = getfield(Main, sym) # Replace `Main` with whatever Module you defined sub-types of `AllKey` in
constructor{T}(x)
end
julia> KeyHolder(FooKey)(FooType, 1)
FooKey{FooType}(1)
julia> KeyHolder(BarKey)(BarType, 2)
BarKey{BarType}(2)
It also disallows ::Function and ::Any in the callable position, just too general to implement method tables. Restricting M<:Integer or some other type won’t work for method tables either, the signature demands a const name or an annotation of a type in the callable position.
And that’s also why M gets treated as a const name. There is actually a way to say “a local variable M for inputs that subtype the parametric AllKey”:
julia> (M::Type{S} where S<:AllKey)(::Type{T}, x::Int) where {T <: AllType} = M{T}(x)
julia> FooKey(FooType, 1)
FooKey{FooType}(1)
julia> BarKey(BarType, 1)
BarKey{BarType}(1)
Type is a type, so its annotation is legal in the callable position. Type relations in where clauses strictly apply to type parameters in type annotations, not argument names. Same thing applies to positional arguments, and we actually get an informative error:
julia> bar(M) where M<:Integer = M
ERROR: syntax: function argument and static parameter name not distinct: "M" around REPL[14]:1
Stacktrace:
[1] top-level scope
@ REPL[14]:1
julia> bar(X::Type{M} where M<:Integer) = X
bar (generic function with 1 method)
julia> bar(Int), bar(UInt16)
(Int64, UInt16)