Yes, it would be a different system without that cycle. I canât articulate what would break, but it seems at least awkward for a dynamic language with multiple dispatch, because you would like some way to dispatch on any type, even if itâs a higher-order type.
Just to illustrate, as you mentioned, that there are types like typeof(...), but not higher:
julia> sin
sin (generic function with 13 methods)
julia> typeof(sin)
typeof(sin)
julia> typeof(typeof(sin))
DataType
I think that, in my initial read, I was distracted by the prescriptive tone of your comments
If <: is indeed a subrelation of isa, and I am interested in type hierarchies, then I hope you will agree that there is no reason why I should not be using isa.
My turn to be prescriptive: the âand so onâ bit is exactly what you should have in any complete description of a type space. This is what higher order reasoning entails.
Please can you (and @Henrique_Becker) clarify what is meant by âa cycle at DataTypeâ? DataType is a node on the graph, no?
I think DataType is acting as a kind of âfixed pointâ to avoid unbounded hierarchies (no cycles here).
Thanks for pointing this out, this is useful information to have.
I have just checked, and I can see that isa is still used in some cases instead of <: . So I donât really see the relevance to my statement. Please feel free to enlighten me.
Yeah, it sounds like you understand what I meant with âcycleâ. I am talking about a cycle because if in your graph there is an edge S â T iff T == typeof(S), then there is a self-loop at S = T = DataType.
I am not sure that I understand your confusion about <: and isa.
julia> Int isa Integer
false
julia> Int <: Integer
true
julia> 3 isa Integer
true
julia> 3 <: Integer
ERROR: TypeError: in <:, expected Type, got a value of type Int64
Where it gets tricky is with the special Type type, which seems like it lets you express the <: relation using isa:
julia> Int <: Integer
true
julia> Int isa Type{<:Integer}
true
I think that S <: T === S isa Type{<:T} for all types S and T.
Note that itâs not the case that all the nodes have that self-loop, rather DataType is unique in that way. So itâs not that the relation R(a, b) = (a == typeof(b)) is reflexive. It is most certainly not.
Sorry if that was confusing. I could have just written == in this case.
Am I right in thinking that Type{<:T} is equivalent to the set of objects that are either at level T or lower according to <: ?
For instance
julia> DataType isa Type{Any}
false
julia> DataType isa Type{<:Any}
true
Assuming you are right, then you have shown that, the restriction of isa to the image of Type{<: } is isomorphic to <: . In other words, you have succeeded in âembeddingâ <: in isa. This is nice. What it does not provide is a characterisation of isa.
Great, so this establishes that <: is not a partial order. Further clarification on what you mean by âuniqueâ would be useful:
julia> DataType <: DataType
true
julia> Any <: Any
true
julia> Type <: Type
true
julia> Int <: Int
true
Is isa is characterised by typeof? if so, then we get reflexivity in the sense that, for every object x
âRelationsâ are a set theory âthingâ, but I think I donât know what set weâre discussing.
For R(a, b) = (b == typeof(a)), we can consider the set of all possible Julia objects (values).
julia> R(a, b) = (b == typeof(a))
R (generic function with 1 method)
julia> R(1, Int64)
true
julia> R(1, Integer)
false
julia> R(Any, Any)
false
julia> R(1, 1)
false
For R_isa(a, b) = (a isa b), you canât:
julia> R_isa(1, Int64)
true
julia> R_isa(1, Integer)
true
julia> R_isa(Any, Any)
true
julia> R_isa(1, 1)
ERROR: TypeError: in isa, expected Type, got a value of type Int64
So thatâs one thing to be clear about. It seems youâre asking set-theoretic questions, but set theory is famously not type theory, and it can be awkward to describe things that have types using set theory.
If you limit the sets to be only Julia objects that satisfy object isa DataType, or do something else to make isa be a Relation (e.g. if isa throws an error, return âfalseâ), the answer is still, âno, isa is not reflexiveâ.
If I could take a step back, is there a different question youâre ultimately interested in answering? What is motivating you to ask these questions about relations?
And taking a step forward, I think youâll find interesting structure if you consider the set of every julia object o such that o isa DataType and:
<: is a Relation. It is reflexive. It is probably supposed to be a partial order, but because the subtyping algorithm is 1. not simple, 2. not completely formalized, I am not confident that it is. I think the intent is that it be a partial order, in any case.
Also intended I believe is that the set of all DataTypes is a bounded lattice.
Any and Union{} as the top and bottom.
Union{S, T} and typeintersect(S, T) are âjoinâ and âmeetâ.
There is some additional structure due to abstract types. typejoin(S, T) is maybe also âmeetâ ? Iâm not sure.
I beg to differ, orders are relevant to type theory. And type theory is still, for all present purposes, still set theory. You would be amazed at how far one can get studying type spaces using sets, amazed. I am happy to provide references.
My purpose is to understand the structure of the type hierarchy. This is what binary relations and functions do: allow you to understand structure. That is my only goal.
Anyway, I think the discourse has come to its natural conclusion with that post.
Yup. I just mean that the implementation of those computational procedures might not actually perfectly exhibit the necessary properties, either due to bugs or for good practical reasons that Iâm not familiar with.
Thanks for this paper. Itâs great to get some clarity, like breathing pure oxygen in fact. Concrete types and the definition of typeof in appendix A should address most of my concerns (assuming I am right in thinking that isa is the binary relation / graph form of typeof).
The difference comes down to values and types. A type is a set of values. <: is the subtyping relation, equivalent to â from set theory. isa gives values a meaning here - X isa Y is equivalent to asking typeof(X) <: Y (or, equivalently, is X in the set described by Y).
This should cover all values except for instances of DataType (e.g. Int, what youâd refer to as âtypeâ), because of the mentioned loops above. Some more information can be found here, the relevant implementation for these builtins can be found here. In particular, isa is implemented here.