I have some problems parsing the Julias type-system… and I came up with a few questions.
Docs - Types: “Type{T} is an abstract type whose only instance is the object T”
How can an abstract type have instances??
As far as I can understand is Type{T} kind of a special thing that has the following defining property: " isa(A,Type{B}) is true iff A and B are the same object and that object is a type". Why has this special thing DataType, Union etc. as subtypes?
julia> subtypes(Type)
4-element Array{Any,1}:
Core.TypeofBottom
DataType
Union
UnionAll
Are all types of some type that is listed above?
Let D be an instance of DataType, thus isa(D, DataType) is true. By the definition of Type{T} is also isa(D,Type{D}) true. This leads me to the conclusion that either DataType <: Type{D} or Type{D} <: DataType must be true. And indeed for D=Int holds the second relation true. But why runnig supertype(Type{Int}) returns Any even though the documentation says “declared types ( DataType ) have unambiguous supertypes” (typeof(Type{Int}) is equal DataType)… Other thing is that isabstracttype(DataType) returns false but Type{Int} <: DataType is true. Is it possible to use non abstract types as supertypes?
I am very confused… if someone knows any other sources that explain this matter I would be grateful. Thanks in advance.
You misunderstood: T can be any type (including an abstract type), while Type{T} is another type, for which the only instance is T. Types can be values, too.
Types (as values) also have a type.
Yes.
No, concrete types cannot have subtypes.
I think the docs are the best source. I would suggest that you think of Type{T} as something which helps you dispatch on types, eg as in
julia> struct Foo end
julia> f(::Foo) = "the value"
f (generic function with 1 method)
julia> f(::Type{Foo}) = "the type"
f (generic function with 2 methods)
julia> f(Foo())
"the value"
julia> f(Foo)
"the type"
and otherwise not worry about the types of types unless you are working on the internals of Julia.
Yes I do understand but Type{T} where T is an abstract type. (isabstracttype(Type{T} where T) is true)
Yes but I don’t get it why should they be a subtype of Type{T} where T if Type{T} where T is something which introduces some special behaviour on its own.
Sadly, it doesn’t seem to be consistent with the behaviour I described. isconcretetype(DataType) returns true
I just wanted to fully understand it but I think you are right, from the user perspective it shouldn’t be a big deal if I don’t have the whole picture. Thanks
That is not the case. The following comes straight from REPL help.
<:(T1, T2)
Subtype operator: returns true if and only if all
values of type T1 are also of type T2.
Examples
≡≡≡≡≡≡≡≡≡≡
julia> Float64 <: AbstractFloat
true
julia> Vector{Int} <: AbstractArray
true
julia> Matrix{Float64} <: Matrix{AbstractFloat}
false
Abstract types cannot be instantiated, and serve only as nodes in the type graph, thereby describing sets of related concrete types: those concrete types which are their descendants.
Again, I would recommend that you read the manual and allow some time to digest it. Figuring out these things by trial and error may be a somewhat confusing approach. YMMV.
5<:Int is not true. It’s an error, since 5 is not a type. Type{Int} <: DataType is true because Type{Int} is a subtype of DataType.
I thinkType is the only case in the language where an abstract type is a subtype of a concrete type. I also found it surprising and confusing when I first encountered it, though I suppose it doesn’t technically contradict anything the docs say.
That’s not true, although the docs seem to suggest that, and I think that’s the main confusing point here.
One example of a concrete type having a subtype is Type{Int} <: DataType, as @samuel noted. Other examples are Union{} <: Int and Tuple{Int,Float64} <: Tuple{Integer,Real}.
EDIT: I also share @samuel’s confusion about the fact that supertype(Type{Int}) !== DataType. Seems like a bug, as far as I understand, but maybe not, since the docs don’t seems to define what “the supertype” means (the docs also mention the terms “unambiguous supertype”, “immediate supertype”, and “direct supertype”, which persumably refer to the same thing supertype is supposed to return).
I prefer to think of the type system the following way:
A tree of abstract types, with concrete types as the leaf nodes. The root is Any. Relevant keywords are abstract type and struct.
Each node in the above tree describes a UnionAll set when parametrized, invariance is the key concept here. The relevant keyword is where.
Tuples, which are in contrast covariant.
TechnicallyDataType and Type{T} are types, and thus <: can be used to compare them. But for most practical code, I don’t think it is advantageous to think of these types as being part of the type hierarchy. Similarly, Base.Bottom / Union{} is there to make a lattice, and is useful for some code manipulating types, but most users can safely ignore it when encountering the Julia type system for the first time. Keep in mind that its main purpose is organizing dispatch and efficient compilation.