I have trouble implementing the dispatch I want using type aliases. I want to dispatch on Type{A{T1,B{T3}}} where {T1,T3}, but i do not find the right syntax. I currently have something that looks like :
struct A{T1,T2}
x::T1
y::T2
end
struct B{T3}
z::T3
end
# an alias :
const C{T1,T3} = A{T1,B{T3}}
# and a constructor :
C(x,z) = A(x,B(z))
c = C(1,2.)
typeof(c) # returns C{Int64, Float64} (alias for A{Int64, B{Float64}})
function myfunction(::Type{A{T1,B{T3}}}) where {T1,T3}
return 1
end
myfunction(C) # Errors....
The function already dispatches on e.g. typeof(c), but not on C itself.
Then you “raise” the G type parameter up to the function level (instead of being buried in T):
julia> function second_function(::Type{T}) where {G, T<:A{T1,G} where T1}
return G
end
second_function (generic function with 1 method)
julia> second_function(C{1,2})
B{2}
This is it. But now why is’nt second_function(C) working ? I understand that C is not a type but a group of types… But I really only want B and not B{T3} to be returned. Afterall,
julia> C
C (alias for A{T1, B{T3}} where {T1, T3})
julia>
So the information that the second slot of C is B{T3} where T3 is there in the object.
I think that’s just a rule of how dispatch with type parameters works — it requires matching against a fully-resolved type. So I don’t think you’ll ever get a type parameter with a where clause inside a function.
Yes this is the behavior I want. But I want is for several types at once :
struct B1{T3}
y::T3
end
const C1{T1,T3} = A{T1,B1{T3}}
struct B2{T3}
y::T3
end
const C2{T1,T3} = A{T1,B2{T3}}
struct B3{T3}
y::T3
end
const C3{T1,T3} = A{T1,B3{T3}}
struct B4{T3}
y::T3
end
const C4{T1,T3} = A{T1,B4{T3}}
# I have like 12 of them
So IIUC I have to define all the methods:
second_function(::Type{Ci}) = inner_method(Bi)
to be able to simply dispatch on this ?
If yes then this is not a huge problem I can copy/paste
That sure looks like you’re wading into some pretty dark and scary woods… there will surely be more dragons lurking about. I’m afraid I don’t have more concrete suggestions given the sketchy nature here, but it looks fraught to me.
I’ve been thinking of it in my head as method where clauses requiring the parameters be specified because they may be matched across multiple arguments or be accessible values in the method body. Reminds me of this past thread, I’ll just share the last small example sans the thread’s context:
As for this particular case:
# works for _(C), fails for _(C{Int, Float64})
function myfunction(::Type{ A{T1,B{T3}} where {T1,T3} })
return 1
end
# names disambiguated to avoid multimethods
# works for _(C{Int, Float64}), fails for _(C)
function myfunction1(::Type{ A{T1,B{T3}} }) where {T1,T3}
return 1
end
function myfunction2(::Type{ A{T1,B{T3}} } where {T1, T3})
return 1
end
As said before, method parameters must be specified, so myfunction1 failing makes sense. myfunction2 fails because the parametric Type requires that the T1 and T3 be specified, in the same way that D = Vector{Ref{T}} where T only includes Vector{Ref{Int}} or Vector{Ref{Float64}}, but not Vector{Ref}.
In fact myfunction1 and myfunction2 would override each other with the same function name because the argument requires specified T1 and T3, despite not being equivalent (T1 and T3 are not available in the method body nor can they matched across arguments).
My hunch is that all these types should just be one type, potentially parameterized, but mostly storing the distinctions between them as data in the fields. It’s likely going to be hard to preserve type stability — especially if you want to have functions returning UnionAlls, for example — and then you might as well just use ifs instead of dispatch in any case.
Right, I wasn’t addressing that, just how type parameters work. You could avoid manual copies and rewrites with metaprogramming, something like:
for i in 1:4
# make Symbols representing the variables
Bi = Symbol('B'*string(i))
Ci = Symbol('C'*string(i))
# @eval this block in the global scope
# @eval allows interpolation of Symbols into the expression
@eval begin
struct $(Bi){T3}
y::T3
end
const $(Ci){T1,T3} = A{T1,$(Bi){T3}}
end
end
But this is assuming that you are making a MWE that doesn’t show the utility, otherwise I would caution you against making so many types with the same structure and methods, that could just be 1 type.
Thanks for the metaprogramming, this is the right solution to meet my criterias indeed. However:
I agree completely with these statements, and therefore if you want to read the following I’ll describe a bit more the issue I am having so that we may or may not conclude on a different API.
I have an abstract Generator type with approx 12 generators implemented (but more expected in the future), with “known” names:
abstract type Generator end
(G::Generator)(x) = # this method should be implemented for all generator.
# then I have several generator:
struct ClaytonGenerator <: Generator end
struct GumbelGenerator <: Generator end
# and all implement the same functor method.
For simplicity i give them no parameters at all but all have fields in them and are fundamentally different from each other.
Then I have the main type of the problem, that implements something on top of a generator:
struct Archimedean{d, TG}
G::TG
function Archimedean(d,G)
new{d,typeof(G)}(G)
end
end
This interface is hidden from the user through a set of aliases:
const Clayton{d} = Archimedean{d, ClaytonGenerator}
Clayton(d,...) = Archimedean(d,ClaytonGenerator(...)) # again, there are parameters in the true model
const Gumbel{d} = Archimedean{d, GumbelGenerator}
Gumbel(d,...) = Archimedean(d,GumbelGenerator(...)) # again, there are parameters in the true model
In fact, the users might not even know that the generators exists (but if they do, they are also exposed), and these names are “known” from the litterature.
All those objects are models, that you can fit to a dataset via :
fit(Clayton,data)
fit(Gumbel,data)
At least, that was the interface I had before, when Clayton, Gumbel, ... where their own types <:Archimedean and the Generators were just methods from these types. But now I have another kind of model, that is supposed to take the same place in the structure as the Archimedean type, and leverage the implementation of the generators.
Since the Generator is in facrt the stuff that needs to be fitted, I wanted to devise, from only the Clayton object, that the right generator to use is the ClaytonGenerator.
So there are two goals :
Keep the outside fitting interface that is already there
Move generators out of the Clayton and Gumbel structs to be able to leverage them at other places of the code.
Ah, then types indeed might be the answer! But in many cases, I’ve found it easier to dispatch on values rather than types, even if those values are just singletons that flag which generator to use.
I might be misunderstanding something then, I was imagining something like generatorof(::Type{Clayton}) = ClaytonGenerator or generatorof(::Clayton) = ClaytonGenerator, depending on if you are passing Clayton itself or its instances.