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

Add a few new reflection methods (after type system merge) #20006

Merged
merged 5 commits into from
Feb 1, 2017

Conversation

Keno
Copy link
Member

@Keno Keno commented Jan 12, 2017

This is informed by attempting to port some packages to the new type system.
I have abstracted out the functionality required by those packages into
documented/tested functions. The hope is to avoid introducing too many
implementation details to packages. These functions can also be implemented
in Compat for older julia versions, such that packages can immediate replace
their use of internal APIs with these functions.

@Keno
Copy link
Member Author

Keno commented Jan 12, 2017

Names subject to bikeshedding of course.

@JeffBezanson
Copy link
Sponsor Member

LGTM. These and various other type-related functions scattered around Base should probably be collected into a TypeUtils module eventually.

base/exports.jl Outdated
@@ -1002,6 +1002,7 @@ export
fieldoffset,
fieldname,
fieldnames,
isabstract,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

needs to go in stdlib doc index

Base.parameter_upper_bound(t::UnionAll, idx)

Determine the upper bound of a type parameter in the underlying type. E.g.:
```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would make a good doctest (though I don't think unexported docstrings' doctests get run by default)

@ararslan
Copy link
Member

ararslan commented Jan 13, 2017

Looks like you've used 3-space indents throughout the diff. For consistency it should be 4 spaces. (Indeed, AFAIK, to get the correct formatting in the resulting markdown from docstrings, the signature actually has to be indented 4 spaces.)

@Keno
Copy link
Member Author

Keno commented Jan 13, 2017

Unintentional. Sometimes editors are too smart for their own good.

@Keno
Copy link
Member Author

Keno commented Jan 13, 2017

@JeffBezanson expressed a preference for not exporting them from base until we create the TypeUtils module. As a result, I've unexported isabstract. To allow packages to use this, the proposed strategy is to go ahead and create a Compat.TypeUtils which re-exports these and provides the Compat-ed version for old versions. In the future that can then become const TypeUtils = Base.TypeUtils in Compat, without requiring any code changes.

@JeffBezanson
Copy link
Sponsor Member

Sorry, you'll have to rebase this.

@Keno
Copy link
Member Author

Keno commented Jan 13, 2017

Rebased + a new commit that allows ignoring ambiguities (in detect_ambiguities) due to type parameters becoming Union{}.

```
"""
unparameterized_type(t::DataType) = t.name.wrapper
unparameterized_type(t::UnionAll) = unparameterized_type(unwrap_unionall(t))
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we could try to give this a better name, although I'm not sure what. I think this is essentially supertype? So maybe something like basetype? In general, I think we've tried to avoid providing this, since it makes it tempting to assume you could re-parameterize it (even though basetype(T){tparam} is not generally a well-defined operation)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(even though basetype(T){tparam} is not generally a well-defined operation)

Could you elaborate on this point?

Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That transform isn't meaningful for an arbitrary type T. It's actually not really even valid for a non-arbitrary type T (for example, f(D::Type{Dict}, K, V) = D{K, V} cannot be inferred, leading to this needed correction: 210d4ec#diff-d4736682004c1996e5d9b1c5c33c8c8e).

In the only cases where it is valid (e.g. you passed a type literal to basetype), you could just write that same type literal without the call to basetype. And the resulting code would also be less error-prone if someone replaced the original type with a type-alias (in addition to being shorter, by virtue of not containing no-op function calls).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's your definition of valid? That function definition you wrote down works for at least some uses.

Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean that it's not valid to infer the output of that example function, and it's brittle to changes in the definition of Dict. Or consider Vector{Int} -- it has multiple base types, and one of the proposals for #4774 would have involved adding another. Thus you can't generally assume that you can still parameterize a type object after extracting the base type and get back something meaningful.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I think I see, the point, but I'm not sure I understand why we can't infer D{K,V}.

Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

D is known to be type-equal to Dict, but it is not known to actually be Dict

@Keno
Copy link
Member Author

Keno commented Jan 22, 2017

Rebased and renamed unparameterized_type to basetype as suggested by @vtjnash

@vtjnash
Copy link
Sponsor Member

vtjnash commented Jan 22, 2017

Not to derail my bikeshedding of unparameterized_type further, but maybe we should only expose typename(T), since that's probably the more useful thing to extract?

@Keno
Copy link
Member Author

Keno commented Jan 22, 2017

What would the semantics of that be?

@Keno
Copy link
Member Author

Keno commented Jan 22, 2017

The motivating example for basetype here is to replace this: https://github.com/JuliaGraphics/ColorTypes.jl/blob/master/src/traits.jl#L154

@vtjnash
Copy link
Sponsor Member

vtjnash commented Jan 22, 2017

hm, that seems tricky. That code is implementing the sorts of dubious assumptions I was mentioning are usually best avoided (usually by implementing similar). Strictly speaking, it doesn't appear to do anything wrong, it's basically just equivalent to assuming that all subtypes of Color contain a field named .parameters[1] and that any other parameters are superfluous. This happens to work, since the subtypes implemented in Colors.jl don't contain any other parameters.

typename would be typename(T::DataType) = T.name, mainly to be used as a replacement for datatype_name

elseif isa(argtypes[2], PartialTypeVar)
ptv = argtypes[2]
tv = ptv.tv
canconst = false
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this depend on lb_certain and ub_certain?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. Two invocations of this functions do not create egal types (because it allocates a new one every time). I though we could do something with that at first, but I had to take that out because of that.

@Keno Keno added this to the 0.6.0 milestone Jan 25, 2017
@Keno
Copy link
Member Author

Keno commented Jan 25, 2017

I've addressed some of the review here. @JeffBezanson @vtjnash, I'd appreciate if you could finish this up, since I'll be traveling the rest of this week and this is required for ColorTypes.

t = argtypes[2]
if isa(t, Const) || isType(t)
return typename_static(t)
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indent is a bit off

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So it is. Sorry, I'm used to sublime doing this all automatically for me, but I've been trying to use Atom more. Will fix

@Keno
Copy link
Member Author

Keno commented Jan 30, 2017

Review comments addressed.

tv::TypeVar
lb_certain::Bool
ub_certain::Bool
PartialTypeVar(tv::TypeVar, lb_certain::ANY, ub_certain::ANY) = new(tv, lb_certain, ub_certain)
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why use ANY here if the arguments have to be Bool?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No reason. Will change.

function _typename(a::Union)
ta = _typename(a.a)
tb = _typename(a.b)
ta == tb ? tb : (ta === Any || tb == Any) ? Any : Union{}
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this use ta === tb? (also tb === Any for consistency)

Copy link
Member Author

@Keno Keno Jan 30, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I don't think it should since Const(T.name) != Const(T.name). tb === Any is fine though.


fComplicatedUnionAll{T}(::Type{T}) = Type{Tuple{S,rand() >= 0.5 ? Int : Float64}} where S <: T
let pub = Base.parameter_upper_bound, x = fComplicatedUnionAll(Real)
@test pub(pub(x, 1), 1) == Real
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do these belong in the reflection tests instead?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I put them here, because at some point I had inference mess up and constant fold this to the wrong answer.

This is informed by attempting to port some packages to the new type system.
I have abstracted out the functionality required by those packages into
documented/tested functions. The hope is to avoid introducing too many
implementation details to packages. These functions can also be implemented
in Compat for older julia versions, such that packages can immediate replace
their use of internal APIs with these functions.
Because of the way methods are specified, there is often spurious
ambiguities due to a type parameter being able to take the value
Union{}, (e.g. Type{T} becomes Type{Union{}}). Since detect_ambiguities
reports these, it can drown out more serious ambiguities among
non-Union{} types. Add a keyword argument to detect_ambiguities,
that ignores all ambiguities that only occur if one of the type
parameters has to be Union{}.
Make inference be able to infer the type constructor. This is a little
tricky, since we don't really have a good way to represent this. It's
not quite `Const(TypeVar(:T,lb,ub))`, because a) the bounds may only
be accurate up to type equality and b) TypeVar is not `isbits`, so
it's not actually egal to the value we'll have at runtime. Additionally
Type{T} already has meaning as a partially constructed type (e.g. an
unwrapped UnionAll), so using T::Type{T} runs the risk of confusing
this with an unwrapped type. Instead, introduce a new Const-like type,
`PartialTypeVar`, which carries the type var and also keeps track of
whether the bounds were egal or only typequal (we don't take advantage
of that yet, but we could in the future).

Additionally, improve the inference of `typename` and allow constant folding
of field accesses on TypeName and SimpleVectors (to be able to constant
fold T.parameters[1]).
@Keno Keno merged commit e0ab6a8 into master Feb 1, 2017
@ararslan ararslan deleted the kf/newreflection branch February 1, 2017 19:44
const TypeName_wrapper_fieldindex = fieldindex(TypeName, :wrapper)

function const_datatype_getfield_tfunc(sv, fld)
if (fld == DataType_name_fieldindex ||
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wrong indent, again

Keno added a commit to Keno/Compat.jl that referenced this pull request Feb 1, 2017
Keno added a commit to Keno/Compat.jl that referenced this pull request Feb 1, 2017
Keno added a commit to Keno/Compat.jl that referenced this pull request Feb 2, 2017
@stevengj stevengj mentioned this pull request Feb 3, 2017
timholy pushed a commit to JuliaLang/Compat.jl that referenced this pull request Feb 4, 2017
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
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants