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.

3 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.

4 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.

3 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

I had this problem again. The solution (well my last post) is not working now. I suspect because I am using Reexport.

The LLM, which knows a trillion things, directed me to this topic/thread! Makes me think I’m the only one who cares. Anyway, it also suggested something I thought about but didnt try (because of some erroneous objection I made to myself about it) It is the obvious thing to do really.

Define a method for show like this

function Base.show(io::IO, ::Type{MyType{nested gobbedlygook needed for inference})
   ...
end

This ought to be foolproof unless another show method does something other than just print the type.

This might be documented somewhere by now. But I thought it useful to update this thread in case someone stumbles on it.

A note: I used the explicit type in the parameter list of the show method, because, I want it to be obvious if me or someone else is using a similar type with a parameter or two changed.

A disadvantage here is that you cannot use this for construction, as you can a const alias. If you want that, you can define the const alias in addition to this show method.

Yes, that all tracks. The type display code will only try to display aliases if they’re public or exported. And the set of available aliases from which it tries to find a nice display from is defined from that list.

Here’s the crux of the problem — you can play around with the export/public list to see how things change:

julia> module Zoo
       struct A{T}; end
       const Dog = A{String}
       const Cat = A{Int}
       const Giraffe = A{<:Number}
       public Dog, Giraffe
       end
Main.Zoo

julia> Zoo.Giraffe{Int}
Giraffe{Int64} (alias for Main.Zoo.A{Int64})

julia> Zoo.Cat
Giraffe{Int64} (alias for Main.Zoo.A{Int64})

Const bindings to types are all type aliases are. Type aliases predate this fancy printing code — they didn’t do this when they were literally created with the word typealias!

1 Like