Mikkel Rev
Mikkel Rev

Reputation: 901

How can you claim: (a) «Abstract types cannot be instantiated» and (b) «T is an instance of the abstract type Type{T}»?

I am reading about types of the Julia language (that is often used for computational science): https://docs.julialang.org/en/v1/manual/types/

Under the heading 'Abstract types' I read that «Abstract types cannot be instantiated». The authors explain that abstract types are just used to define what is a supertype of something else. «Alright, that's useful and all good». However, under the heading 'Type{T} type selectors' I read that «Type{T} is an abstract parametric type whose only instance is the object T». How is that possible?

So first the author said that it is true that 'a defining characteristic of an abstract type is that it cannot be instantiated'. Then the author said the same statement is false (because T is an instance of an abstract type, Type{T} ). What does the author mean by these seemingly conflicting statements?

Upvotes: 4

Views: 174

Answers (1)

Bogumił Kamiński
Bogumił Kamiński

Reputation: 69829

What I write here is not an official interpretation by Julia core developers.

First let us understand what Type{T} is as it is non standard (and that is why there is a separate section in the Julia Manual about it). I use Int as T to be specific.

First:

julia> isabstracttype(Type{Int})
true

julia> isconcretetype(Type{Int})
false

Which means that Type{Int} is considered abstract and not-concrete by Julia.

This consideration is relevant in some codes that would rely on these two functions (rare, but possible).

Now what is so special about Type{Int}? Have a look at the following:

julia> typeof(Int)
DataType

julia> Type{Int} <: DataType
true

julia> DataType <: Type{Int}
false

julia> supertype(Type{Int})
Any

julia> isabstracttype(DataType)
false

julia> isconcretetype(DataType)
true

What do we see here:

  • If you check type of Int it is DataType. DataType is concrete and not-abstract (as opposed to Type{Int}).
  • Although DataType is concrete and not-abstract it has a subtype, e.g. Type{Int}.
  • Although Type{Int} is a subtype of DataType its supertype is Any if you want to check it.

This is not the end of the story. Now check Type without a parameter:

julia> supertype(DataType)
Type{T}

julia> subtypes(Type{T} where T)
4-element Vector{Any}:
 Core.TypeofBottom
 DataType
 Union
 UnionAll

julia> supertype(DataType)
Type{T}

julia> subtypes(Type{T} where T)
4-element Vector{Any}:
 Core.TypeofBottom
 DataType
 Union
 UnionAll

julia> Type{Union{Int, Missing}} <: DataType
false

julia> Type{Vector} <: DataType
false

julia> Type{Vector{Int}} <: DataType
true

What we learn here is that Type is a supertype of DataType. Additionally, not all "type specifiers" are subtypes of DataType, as Union and UnionAll types are not DataType. These considerations are quite important in more advanced generic Julia codes.

Now let us discuss a bit a convention that the Julia Manual uses (i.e. I discuss the difference between statements "to be instantiated" and "is an instance"):

  • when it says that some type can be instantiated then it means that there is some value of a given type; you can check this concrete type using the typeof;
  • when it says that some value is an instance of some type it follows the rule (I copy it verbatim as all written there is relevant - essentially a value can be an instance of an abstract type as well as of a concrete type):

When appended to an expression computing a value, the :: operator is read as "is an instance of". It can be used anywhere to assert that the value of the expression on the left is an instance of the type on the right. When the type on the right is concrete, the value on the left must have that type as its implementation – recall that all concrete types are final, so no implementation is a subtype of any other. When the type is abstract, it suffices for the value to be implemented by a concrete type that is a subtype of the abstract type.


In summary. In Julia there was a need to add a convenient way to pass a type as a value. Let me give two examples:

julia> parse(Int, "12")
12

julia> rand(Int)
-2055168985030383807

Therefore the design that was decided on was:

  • to make every type an instance of abstract Type type;
  • DataType captures explicitly declared types, like Int, but it does not capture things like Union or UnionAll; every explicitly declared type is an instance of DataType
  • However, DataType is too vague in many cases (especially for dispatch) and additionally as we have said above it does not cover all possible type specifications; therefore there was a need to introduce Type{T} i.e. add parameter to Type; it is special and that is why the Julia Manual calls it Type{T} type selector (as its use is to be type selector).

To meet all these requirements:

  • Int is an instance of DataType so DataType is concrete;
  • at the same time we want Int to be an instance of Type{Int}; since Type{Int} cannot be a supertype of DataType so it must be its subtype although DataType is concrete; therefore it must be abstract as the Julia Manual states: "that concrete types may not subtype each other" (i.e. concrete type cannot have a concrete subtype; indeed later it also states "all concrete types are final and may only have abstract types as their supertypes" which would seem to suggest that it cannot have subtypes; but in fact Type{Int} etc. is an exception - i.e. DataType is allowed to have an abstract subtype; but Julia makes sure that it is not possible to create a concrete subtype of Type{Int} so the main statement holds - it is not possible to create a concrete subtype of DataType)

I agree that this is a bit "playing with words", but in practice having Type{T} is useful and because of the rules I have described above it does not introduce inconsistencies.


Having said all this let me refer to your statements:

«Abstract types cannot be instantiated»

This is true, e.g. Type{Int} cannot be instantiated since it is abstract.

«Type{T} is an abstract parametric type whose only instance is the object T»

This is also true as values can be instances of abstract types, like Int is an instance of Type{Int} just like any value in Julia is an instance of Any.

Upvotes: 5

Related Questions