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

syntactic sugar Foo{<:Bar} for Foo{T} where T<:Bar #20414

Merged
merged 6 commits into from
Feb 3, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ New language features
`function inv(M::Matrix{T}) where T<:AbstractFloat`.
Anonymous functions can have type parameters via the syntax
`((x::Array{T}) where T<:Real) -> 2x`.
* Implicit type parameters, e.g. `Vector{<:Real}` is equivalent to
`Vector{T} where T<:Real`, and similarly for `Vector{>:Int}` ([#20414]).
* Much more accurate subtype and type intersection algorithms. Method sorting and
identification of equivalent and ambiguous methods are improved as a result.

Expand Down
27 changes: 23 additions & 4 deletions doc/src/manual/types.md
Original file line number Diff line number Diff line change
Expand Up @@ -577,7 +577,7 @@ false
This last point is *very* important: even though `Float64 <: Real` we **DO NOT** have `Point{Float64} <: Point{Real}`.

In other words, in the parlance of type theory, Julia's type parameters are *invariant*, rather
than being covariant (or even contravariant). This is for practical reasons: while any instance
than being [covariant (or even contravariant)](https://en.wikipedia.org/wiki/Covariance_and_contravariance_%28computer_science%29). This is for practical reasons: while any instance
of `Point{Float64}` may conceptually be like an instance of `Point{Real}` as well, the two types
have different representations in memory:

Expand All @@ -603,15 +603,18 @@ function norm(p::Point{Real})
end
```

The correct way to define a method that accepts all arguments of type `Point{T}` where `T` is
A correct way to define a method that accepts all arguments of type `Point{T}` where `T` is
a subtype of `Real` is:

```julia
function norm{T<:Real}(p::Point{T})
function norm(p::Point{<:Real})
sqrt(p.x^2 + p.y^2)
end
```

(Equivalently, one could define `function norm{T<:Real}(p::Point{T})` or
`function norm(p::Point{T} where T<:Real)`; see [UnionAll Types](@ref).)

More examples will be discussed later in [Methods](@ref).

How does one construct a `Point` object? It is possible to define custom constructors for composite
Expand Down Expand Up @@ -711,6 +714,17 @@ julia> Pointy{Real} <: Pointy{Float64}
false
```

The notation `Pointy{<:Real}` can be used to express the Julia analogue of a
*covariant* type, while `Pointy{>:Int}` the analogue of a *contravariant* type,
Copy link
Member

Choose a reason for hiding this comment

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

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 added a link above.

but technically these represent *sets* of types (see [UnionAll Types](@ref)).
```jldoctest pointytype
julia> Pointy{Float64} <: Pointy{<:Real}
true

julia> Pointy{Real} <: Pointy{>:Int}
true
```

Much as plain old abstract types serve to create a useful hierarchy of types over concrete types,
parametric abstract types serve the same purpose with respect to parametric composite types. We
could, for example, have declared `Point{T}` to be a subtype of `Pointy{T}` as follows:
Expand Down Expand Up @@ -740,6 +754,9 @@ This relationship is also invariant:
```jldoctest pointytype
julia> Point{Float64} <: Pointy{Real}
false

julia> Point{Float64} <: Pointy{<:Real}
true
```

What purpose do parametric abstract types like `Pointy` serve? Consider if we create a point-like
Expand Down Expand Up @@ -990,10 +1007,12 @@ Using explicit `where` syntax, any subset of parameters can be fixed. For exampl

Type variables can be restricted with subtype relations.
`Array{T} where T<:Integer` refers to all arrays whose element type is some kind of `Integer`.
The syntax `Array{<:Integer}` is a convenient shorthand for `Array{T} where T<:Integer`.
Type variables can have both lower and upper bounds.
`Array{T} where Int<:T<:Number` refers to all arrays of `Number`s that are able to contain `Int`s
(since `T` must be at least as big as `Int`).
The syntax `where T>:Int` also works to specify only the lower bound of a type variable.
The syntax `where T>:Int` also works to specify only the lower bound of a type variable,
and `Array{>:Int}` is equivalent to `Array{T} where T>:Int`.

Since `where` expressions nest, type variable bounds can refer to outer type variables.
For example `Tuple{T,Array{S}} where S<:AbstractArray{T} where T<:Real` refers to 2-tuples whose first
Expand Down
22 changes: 21 additions & 1 deletion src/julia-syntax.scm
Original file line number Diff line number Diff line change
Expand Up @@ -1763,6 +1763,20 @@
body
(expand-where (expand-wheres body (cdr vars)) (car vars))))

; given e = (curly T params...), return (newparams . whereparams) where any <:X expression
; in params is converted to T and T<:X is added to whereparams; similarly for >:X.
; (This implements the syntactic sugar Foo{<:Bar} --> Foo{T} where T<:Bar.)
(define (extract-implicit-whereparams e)
(define (extract params newparams whereparams)
(if (null? params)
(cons (reverse newparams) (reverse whereparams))
(let ((p (car params)))
(if (and (list? p) (= (length p) 3) (eq? (car p) 'call) (or (eq? (cadr p) '|<:|) (eq? (cadr p) '|>:|)))
(let ((T (gensy)))
(extract (cdr params) (cons T newparams) (cons (list (cadr p) T (caddr p)) whereparams)))
(extract (cdr params) (cons p newparams) whereparams)))))
(extract (cddr e) '() '()))

;; table mapping expression head to a function expanding that form
(define expand-table
(table
Expand Down Expand Up @@ -1980,7 +1994,13 @@
(expand-forms (partially-expand-ref e)))))

'curly
(lambda (e) (expand-forms `(call (core apply_type) ,@(cdr e))))
(lambda (e)
(let* ((p (extract-implicit-whereparams e))
(curlyparams (car p))
(whereparams (cdr p)))
(if (null? whereparams)
(expand-forms `(call (core apply_type) ,@(cdr e)))
(expand-forms `(where (curly ,(cadr e) ,@curlyparams) ,@whereparams)))))

'call
(lambda (e)
Expand Down
27 changes: 27 additions & 0 deletions test/subtype.jl
Original file line number Diff line number Diff line change
Expand Up @@ -861,3 +861,30 @@ f18348{T<:Any}(::Type{T}, x::T) = 2
# Issue #12721
f12721{T<:Type{Int}}(::T) = true
@test_throws MethodError f12721(Float64)

# implicit "covariant" type parameters:
type TwoParams{S,T}; x::S; y::T; end
@test TwoParams{<:Real,<:Number} == (TwoParams{S,T} where S<:Real where T<:Number) ==
(TwoParams{S,<:Number} where S<:Real) == (TwoParams{<:Real,T} where T<:Number)
@test TwoParams(3,0im) isa TwoParams{<:Real,<:Number}
@test TwoParams(3,"foo") isa TwoParams{<:Real}
@test !(TwoParams(3im,0im) isa TwoParams{<:Real,<:Number})
@test !(TwoParams(3,"foo") isa TwoParams{<:Real,<:Number})
ftwoparams(::TwoParams) = 1
ftwoparams(::TwoParams{<:Real}) = 2
ftwoparams(::TwoParams{<:Real,<:Real}) = 3
@test ftwoparams(TwoParams('x',3)) == 1
@test ftwoparams(TwoParams(3,'x')) == 2
@test ftwoparams(TwoParams(3,4)) == 3
@test !([TwoParams(3,4)] isa Vector{TwoParams{<:Real,<:Real}})
@test TwoParams{<:Real,<:Real}[TwoParams(3,4)] isa Vector{TwoParams{<:Real,<:Real}}
@test [TwoParams(3,4)] isa Vector{<:TwoParams{<:Real,<:Real}}
@test [TwoParams(3,4)] isa (Vector{TwoParams{T,T}} where T<:Real)

# implicit "contravariant" type parameters:
@test TwoParams{>:Int,<:Number} == (TwoParams{S,T} where S>:Int where T<:Number) ==
(TwoParams{S,<:Number} where S>:Int) == (TwoParams{>:Int,T} where T<:Number)
@test TwoParams(3,0im) isa TwoParams{>:Int,<:Number}
@test TwoParams{Real,Complex}(3,0im) isa TwoParams{>:Int,<:Number}
@test !(TwoParams(3.0,0im) isa TwoParams{>:Int,<:Number})
@test !(TwoParams(3,'x') isa TwoParams{>:Int,<:Number})