How to dispatch on a type alias?

You did not misunderstood, this kind of method definition is the right thing to do indeed. What you may have misunderstood is that i wanted to define all of them at once, and not them one by one:

For any XXX defined as follows:

const XXX{d,T} <: Archimedean{d,XXXGenerator{T}},

i want the method generator(::type{MyTypeAlias}) = MytypeAliasGenerator to exists.

and that is not possible wihtout metaprogramming.

I actually did misunderstand, it slipped my mind that those are aliases. generatorof(::Archimedian{d, Gen} where d) where {Gen} = Gen extracts a parameter from an instance. Don’t need metaprogramming for that. However, you do need the parameter to have a value for the extracted parameter, which your aliases do. Actually let me fire up the REPL again for a better example.

EDIT: okay this one works on your aliases. Note that it works specifically on the 1-parameter types being aliased, not concrete subtypes or the supertype Archimedean.

julia> const Clayton{d} = Archimedean{d, ClaytonGenerator}
Clayton (alias for Archimedean{d, ClaytonGenerator} where d)

julia> generatorof(::Type{Archimedean{d, Gen} where d}) where Gen = Gen
generatorof (generic function with 1 method)

julia> generatorof(Clayton)
ClaytonGenerator

julia> generatorof(Archimedean{1, ClaytonGenerator}) # fails if concrete
ERROR: MethodError: no method matching generatorof(::Type{Clayton{1}})
...
1 Like

Hum… I tried to translate your code to my starting example and it fails :

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))

generatorof(::Type{A{T1, Gen} where T1}) where Gen = Gen
# generatorof(::A{T1,Gen} where T1) where {Gen} = Gen

generatorof(C) 

Do this require Gen to be a singleton ? On your side, can you make hte same thing for const Clayton{d,T} = Archimedean{d,ClaytonGenerator{T}} ?

It’s more apparent in the REPL printout, but C has 2 parameters:

julia> const C{T1,T3} = A{T1,B{T3}}
C (alias for A{T1, B{T3}} where {T1, T3})

The way generatorof is set up, it takes only parametric types varying on 1 parameter. Remember what I said earlier, method parameters must have specific values. It worked for Clayton because Gen = ClaytonGenerator.

Yes indeed. Is it posible to add variance on the second one too ?

Yes, but it will depend on what you want to extract, a particular concrete type like ClaytonGenerator{1} or the parametric ClaytonGenerator{T} where T. In the first case, you’d pass in Clayton{d, 1}, in the second you’d just pass in Clayton. Could just keep 2 methods around for both purposes. Probably better to figure out the structure of your input and output types first before implementing the method.

I want to pass Clayton and extract ClaytonGenerator :slight_smile:

Right, but it’ll depend on how you’re setting up the Clayton alias, it’s not clear anymore how many unspecified parameters it should have.

Ok so let’s specify it again give me a sec.

Edit: here is an up to date spec:

abstract type Generator end
struct Archimedean{d,T}
    G::T
end
struct ClaytonGenerator{T} <: Generator
    θ::T
end
const Clayton{d,T} = Archimedean{d, ClaytonGenerator{T}}


#generatorof(Clayton) -> Claytongenerator

# But also if i a user defines
struct GumbelGenerator{T} <: Generator
    θ::T
end
const Gumbel{d,T} = Archimedean{d, GumbelGenerator{T}}

# generator(Gumbel) should return GumbelGenerator

I see why it’s tricky, the earlier advice to use the REPL printout would mislead:

julia> const Gumbel{d,T} = Archimedean{d, GumbelGenerator{T}}
Gumbel (alias for Archimedean{d, GumbelGenerator{T}} where {d, T})

julia> generatorof(::Type{Archimedean{d, Gen{T}} where {d, T}}) where Gen = Gen
ERROR: TypeError: in Type{...} expression, expected UnionAll, got a value of type TypeVar
...

Type aliasing doesn’t introduce a 3rd parameter like that. In a shorter example:

julia> struct X{T, S} end

julia> Y{T} = X{T, Ref{T}}
Y (alias for X{T, Ref{T}} where T)

julia> X{T, Blah{T}} where {T, Blah}
ERROR: TypeError: in Type{...} expression, expected UnionAll, got a value of type TypeVar
...

The type system only sees X{T, Ref{T}} as a subtype of X{T, S}, not any type with a 3rd parameter; Y’s aliasing does not create any types. It’s not any different from Z{T} = Ref{T} not creating a supertype Blah{T}, ad-hoc supertyping is just too chaotic.

You can make a method getBlah(::Type{Y}) = Ref, and you could use the aforementioned metaprogramming to make more boilerplate methods for the others.

Only a full type can have a 3rd parameter, but I don’t think this is worth doing:

julia> struct X3{T, Blah, S} # cannot do Blah{T} with unknown Blah
         # constructor lacks S, but S remains as parameter
                                      # Blah is known here
         X3{T, Blah}() where {T, Blah} = new{T, Blah, Blah{T}}()
       end

julia> X3{Int, Ref}()
X3{Int64, Ref, Ref{Int64}}()

julia> X3{Int, Ref} # REPL now prints shorthand annoyingly
X3{Int64, Ref}

julia> X3 # ditto
X3

julia> X3{Int, Ref}.body # prints S
X3{Int64, Ref, S}

julia> X3.body.body.body # I forgot what internal method does this
X3{T, Blah, S}
1 Like

@mbauman @Benny just to tell you that I ended up using the dumb approach, as i could not find a better way without resorting to meta-programming, which i do not like. The true problem and its end result are there : https://github.com/lrnv/Copulas.jl/blob/0b5822810077a50bf062be892be1d8051f03c484/src/ArchimedeanCopula.jl#L216-L254

Not pretty, but working :joy: Thanks to both of you anyway

4 Likes

“dumb” is subjective, but this @eval looping over Symbols does exist in Base Julia to implement many operators. You are right that it’s worth refactoring to a simpler system with a fewer types and methods when possible, but sometimes you really do need many very similar versions.

2 Likes

Based on Benny’s instruction (I mean the part of which I sort of understood):

julia> generatorof(::Type{S}) where {S <: Archimedean} =
  S.body.body.parameters[2].name.wrapper
generatorof (generic function with 1 method)

julia> generatorof(Clayton)
ClaytonGenerator

julia> generatorof(Gumbel)
GumbelGenerator

seems to get the job done (not so pretty for sure).

Or even less pretty:

function generatorof(::Type{S}) where {S <: Archimedean}
    S2 = hasproperty(S,:body) ? S.body : S
    S3 = hasproperty(S2, :body) ? S2.body : S2
    S3.parameters[2].name.wrapper
end

which covers the different cases (i.e. not just the alias but also concrete types).

2 Likes

@Dan I dont know how you did that, but thanks a lot. I will commit this, link this thread, and never think about it again, dear wizzards :stuck_out_tongue:

So I am trying to use it and implement the meta-programming proposed by @Benny upper in this thread to complement the solution, but I am still having an issue… my metaprogramming skills are not very good.

Take a look here : Automatic aliasing of archimedeans by lrnv · Pull Request #82 · lrnv/Copulas.jl · GitHub for the implementation and the error that I am getting. Maybe you’ll see quickly the problem

Not quickly, but maybe:

ArchimedeanCopula

should be

Archimedean

?

Subtypes of both all have the name structured as “SomethingGenerator” and i want to construct the “SomethingCopula” that corerspond, which is what the string manipulation does.

Definitely an option to dig into parameters like this, but I tend to avoid internal details that could be changed in a minor revision; it’s not very often because people don’t want to change a bunch of internal base Julia on a whim. On the other hand, dispatching on subtypes is exposed as language feature.

It’s complaining about an undefined variable Copulas.AMHGenerator when you hit the metaprogramming lines. It just occurred to me that was bizarre because the module and type should not be 1 variable name, but 2. Here’s a MWE of the effect:

julia> module A end
Main.A

julia> A.X
ERROR: UndefVarError: X not defined
...
julia> var"A.X"
ERROR: UndefVarError: A.X not defined

julia> @eval $(Symbol("A.X")) = 0
0

julia> var"A.X"
0

julia> A.X
ERROR: UndefVarError: X not defined
...

I’m not exactly sure when this happens, but string on some of the types you get from InteractiveUtils.subtypes included the modules in the string, and baking that into a symbol makes a bizarre variable that is not the same as the type. Specifically, you can rest assured that AMHGenerator exists in the module Copulas, but var"Copulas.AMHGenerator" does not. You can definitely fix the string processing, but @eval metaprogramming typically starts with lists of explicit symbols or strings for easier discovery and handling, in your case you could start with a list of prefixes. Still, I do see the logic of deriving it at runtime from all existing types, so maybe this is a difficulty worth handling.

1 Like

As usaul, I’ll try to interpret the gospel of Benny:

for T in InteractiveUtils.subtypes(ZeroVariateGenerator)
    G = Symbol(last(split(string(T),'.')))
    C = Symbol(string(G)[begin:end-9]*"Copula")
    @eval begin
        const ($C){d} = ArchimedeanCopula{d,(Copulas.$G)}
        ($C)(d) = ArchimedeanCopula(d,(Copulas.$G)())
    end
end
for T in InteractiveUtils.subtypes(UnivariateGenerator)
    G = Symbol(last(split(string(T),'.')))
    C = Symbol(string(G)[begin:end-9]*"Copula")
    @eval begin
        const ($C){d,Tθ} = ArchimedeanCopula{d,(Copulas.$G){Tθ}}
        ($C)(d,θ) = ArchimedeanCopula(d,(Copulas.$G)(θ))
    end
end

passes the precompile phase on my machine.

2 Likes

That should work within the Copulas.___Generator types, but don’t canonize me :laughing: I’m not even Catholic

1 Like

That was it. Thanks a lot to both of you. Nothing religious, but there should be a “compiler wizard” badge on this forum so that you can both earn it ! :slight_smile:

Such a long journey and so many brains to solve this, its impresive how far it can get.

Edit: Maybe “Type wizard” is pedentic enough

2 Likes