Reputation: 901
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
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:
Int
it is DataType
. DataType
is concrete and not-abstract (as opposed to Type{Int}
).DataType
is concrete and not-abstract it has a subtype, e.g. Type{Int}
.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"):
typeof
;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:
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
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;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