Why is a const binding not always a type alias?

With this

struct A{T}
end

const Dog = A{String}
const Cat = A{Int}

I have

julia> Dog
Dog (alias for A{String})

julia> Cat
Cat (alias for A{Int64})

But with this

struct A{T}
end

const Dog = A{String}
const Cat = A{Int}
const Giraffe = A{<:Number}

I have

julia> Dog
Dog (alias for A{String})

julia> Cat
A{Int64}

julia> Giraffe
Giraffe (alias for A{<:Number})

Why is Cat no longer an alias in the latter case? In my application, which is a bit more verbose, I want to have both Cat and Giraffe show as aliases. I use Giraffe for parameter annotations that occur many times in method definitions.

2 Likes

I don’t know why, but does it make any difference in practice? It seems that you can still use both as aliases for parameter annotations, don’t you?

You loose the pretty printing, apparently, is that it?

julia> x = A{Int}()
Cat()

julia> const Giraffe = A{<:Number}
Giraffe{var"#s1"} where var"#s1"<:Number (alias for A{var"#s1"} where var"#s1"<:Number)

julia> x = A{Int}()
A{Int64}()

Probably something related to the ambiguity (A{Int}() is a Cat or a Giraffe?). Removing it solves the problem:

julia> struct A{T} end

julia> const Cat = A{Int}
Cat (alias for A{Int64})

julia> const Giraffe = A{<:Union{AbstractFloat,Complex}}
Giraffe{var"#s1"} where var"#s1"<:Union{AbstractFloat, Complex} (alias for A{var"#s1"} where var"#s1"<:Union{AbstractFloat, Complex})

julia> A{Int}()
Cat()

julia> A{Float64}()
Giraffe{Float64}()


Printing type aliases is surprisingly hard, so there is a good chance that you’re hitting a corner case in the code.
Likely worth opening an issue.

3 Likes

But it is indeed an Alias, right? It is just a matter of printing.

Yes, nothing has changed about the type, just the way it’s displayed.

Yes, nothing has changed about the type, just the way it’s displayed.

But it is indeed an Alias, right? It is just a matter of printing.

Yes, absolutely. It’s, as far as I can tell, just a matter of printing. But, the printing is apparently all that distinguishes a type alias from any other binding with a const declaration; So in this sense, Cat above is no longer a type alias. In any case, I have a rather large package and the Cat alias is user facing. Having the printing depend on another variable binding seems fragile. Or maybe there is a good reason for it and I just can’t have what I want.

I’m inclined to agree with @sdanisch that this is unintentional.

I think it is, but consider that there is that ambiguity (A{Int}() is both a Cat and a Giraffe). Probably one would like that the alias referring to least general subtype is printed, but that has to be a decision that someone has to take.

2 Likes

Yes, my expectation is that the most specific alias is printed. I left this unsaid, but it should be explicit.

Here is more unexpected behavior: Wrap the code above in a module. Then choose one of

export Cat, Dog, Giraffe

or

export Cat, Dog

Now, whether Cat prints as an alias depends on whether Giraffe appears in the export list. Importantly, it does not depend on whether I import (or using) any of Cat, Dog, or Giraffe. I can import any or all of the three. Or import none of them and refer to each of them by its fully qualified name. The alias status of Giraffe (when printing) only overrides that of Cat if both of them appear in the lists of exported symbols in the module.

1 Like