Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

redesign typeof(::Type) #29368

Open
JeffBezanson opened this issue Sep 25, 2018 · 3 comments
Open

redesign typeof(::Type) #29368

JeffBezanson opened this issue Sep 25, 2018 · 3 comments
Labels
domain:types and dispatch Types, subtyping and method dispatch kind:breaking This change will break code
Milestone

Comments

@JeffBezanson
Copy link
Sponsor Member

A type in Julia takes one of four forms: DataType, Union, UnionAll, or Bottom. These are represented as Julia structs, distinguished by ordinary type tags for each of the four. This arrangement creates some subtle problems.

Types can be compared two ways: structurally (===, "looks the same when printed") and semantically (==, what set of values does it represent). For example Int == Union{Int,Int} but they have different structures (actually we normalize the union to just Int, but in theory such a union type can exist). The representation of a type, which is what you currently get from typeof, only involves its structure and not semantics.

The problem is that the type system prefers to compare types semantically. For example A{X} <: A{Y} iff X == Y (semantic comparison). However it is also possible to dispatch on type representations. For example

f(t::DataType) = 0
f(t::Union) = 1
g(::Type{T}) where {T} = f(T)

A call like g(Int) does not have a clear answer --- strictly speaking we only know that T == Int, not its representation. We currently have to use various hacks to get around this. One such hack is that inference is suboptimal in cases like this:

foo(f) = Ref((f,))
@code_typed foo(Complex) # => RefValue{_1} where _1

A related issue is that types like DataType and Type{Int} have an overly-complex subtype and specificity relationship: one is not a subtype of the other, but their intersection is non-empty, and we're not able to accurately represent that intersection. For various reasons (including backwards compatibility) subtyping currently gets this wrong on purpose, making Type{Int} <: DataType true.

I've come to think we'd be better off getting rid of DataType et. al., and not using type tags to identify type representations. Instead, for instance, we could make typeof(t) give Type for every type, and you'd have to look at fields to determine whether you have a Union, UnionAll, etc.

cc @vtjnash @jrevels

@JeffBezanson JeffBezanson added kind:breaking This change will break code domain:types and dispatch Types, subtyping and method dispatch labels Sep 25, 2018
@JeffBezanson JeffBezanson added this to the 2.0+ milestone Sep 25, 2018
@StephenVavasis
Copy link
Contributor

Just wondering if there is any connection between this issue and the following very old issue (which affected me once)

#10947

@JeffBezanson
Copy link
Sponsor Member Author

Yes it's connected; if we made this change we would probably have typeof((Int,)) == Tuple{Type{Int}}.

@JeffreySarnoff
Copy link
Contributor

🍉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
domain:types and dispatch Types, subtyping and method dispatch kind:breaking This change will break code
Projects
None yet
Development

No branches or pull requests

3 participants