Skip to content

Commit c47412b

Browse files
authored
Merge pull request #20414 from stevengj/covariant
syntactic sugar Foo{<:Bar} for Foo{T} where T<:Bar
2 parents c52bad9 + 272ca05 commit c47412b

File tree

4 files changed

+73
-5
lines changed

4 files changed

+73
-5
lines changed

NEWS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ New language features
1414
`function inv(M::Matrix{T}) where T<:AbstractFloat`.
1515
Anonymous functions can have type parameters via the syntax
1616
`((x::Array{T}) where T<:Real) -> 2x`.
17+
* Implicit type parameters, e.g. `Vector{<:Real}` is equivalent to
18+
`Vector{T} where T<:Real`, and similarly for `Vector{>:Int}` ([#20414]).
1719
* Much more accurate subtype and type intersection algorithms. Method sorting and
1820
identification of equivalent and ambiguous methods are improved as a result.
1921

doc/src/manual/types.md

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -577,7 +577,7 @@ false
577577
This last point is *very* important: even though `Float64 <: Real` we **DO NOT** have `Point{Float64} <: Point{Real}`.
578578

579579
In other words, in the parlance of type theory, Julia's type parameters are *invariant*, rather
580-
than being covariant (or even contravariant). This is for practical reasons: while any instance
580+
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
581581
of `Point{Float64}` may conceptually be like an instance of `Point{Real}` as well, the two types
582582
have different representations in memory:
583583

@@ -603,15 +603,18 @@ function norm(p::Point{Real})
603603
end
604604
```
605605

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

609609
```julia
610-
function norm{T<:Real}(p::Point{T})
610+
function norm(p::Point{<:Real})
611611
sqrt(p.x^2 + p.y^2)
612612
end
613613
```
614614

615+
(Equivalently, one could define `function norm{T<:Real}(p::Point{T})` or
616+
`function norm(p::Point{T} where T<:Real)`; see [UnionAll Types](@ref).)
617+
615618
More examples will be discussed later in [Methods](@ref).
616619

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

717+
The notation `Pointy{<:Real}` can be used to express the Julia analogue of a
718+
*covariant* type, while `Pointy{>:Int}` the analogue of a *contravariant* type,
719+
but technically these represent *sets* of types (see [UnionAll Types](@ref)).
720+
```jldoctest pointytype
721+
julia> Pointy{Float64} <: Pointy{<:Real}
722+
true
723+
724+
julia> Pointy{Real} <: Pointy{>:Int}
725+
true
726+
```
727+
714728
Much as plain old abstract types serve to create a useful hierarchy of types over concrete types,
715729
parametric abstract types serve the same purpose with respect to parametric composite types. We
716730
could, for example, have declared `Point{T}` to be a subtype of `Pointy{T}` as follows:
@@ -740,6 +754,9 @@ This relationship is also invariant:
740754
```jldoctest pointytype
741755
julia> Point{Float64} <: Pointy{Real}
742756
false
757+
758+
julia> Point{Float64} <: Pointy{<:Real}
759+
true
743760
```
744761

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

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

9981017
Since `where` expressions nest, type variable bounds can refer to outer type variables.
9991018
For example `Tuple{T,Array{S}} where S<:AbstractArray{T} where T<:Real` refers to 2-tuples whose first

src/julia-syntax.scm

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1763,6 +1763,20 @@
17631763
body
17641764
(expand-where (expand-wheres body (cdr vars)) (car vars))))
17651765

1766+
; given e = (curly T params...), return (newparams . whereparams) where any <:X expression
1767+
; in params is converted to T and T<:X is added to whereparams; similarly for >:X.
1768+
; (This implements the syntactic sugar Foo{<:Bar} --> Foo{T} where T<:Bar.)
1769+
(define (extract-implicit-whereparams e)
1770+
(define (extract params newparams whereparams)
1771+
(if (null? params)
1772+
(cons (reverse newparams) (reverse whereparams))
1773+
(let ((p (car params)))
1774+
(if (and (list? p) (= (length p) 3) (eq? (car p) 'call) (or (eq? (cadr p) '|<:|) (eq? (cadr p) '|>:|)))
1775+
(let ((T (gensy)))
1776+
(extract (cdr params) (cons T newparams) (cons (list (cadr p) T (caddr p)) whereparams)))
1777+
(extract (cdr params) (cons p newparams) whereparams)))))
1778+
(extract (cddr e) '() '()))
1779+
17661780
;; table mapping expression head to a function expanding that form
17671781
(define expand-table
17681782
(table
@@ -1980,7 +1994,13 @@
19801994
(expand-forms (partially-expand-ref e)))))
19811995

19821996
'curly
1983-
(lambda (e) (expand-forms `(call (core apply_type) ,@(cdr e))))
1997+
(lambda (e)
1998+
(let* ((p (extract-implicit-whereparams e))
1999+
(curlyparams (car p))
2000+
(whereparams (cdr p)))
2001+
(if (null? whereparams)
2002+
(expand-forms `(call (core apply_type) ,@(cdr e)))
2003+
(expand-forms `(where (curly ,(cadr e) ,@curlyparams) ,@whereparams)))))
19842004

19852005
'call
19862006
(lambda (e)

test/subtype.jl

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -861,3 +861,30 @@ f18348{T<:Any}(::Type{T}, x::T) = 2
861861
# Issue #12721
862862
f12721{T<:Type{Int}}(::T) = true
863863
@test_throws MethodError f12721(Float64)
864+
865+
# implicit "covariant" type parameters:
866+
type TwoParams{S,T}; x::S; y::T; end
867+
@test TwoParams{<:Real,<:Number} == (TwoParams{S,T} where S<:Real where T<:Number) ==
868+
(TwoParams{S,<:Number} where S<:Real) == (TwoParams{<:Real,T} where T<:Number)
869+
@test TwoParams(3,0im) isa TwoParams{<:Real,<:Number}
870+
@test TwoParams(3,"foo") isa TwoParams{<:Real}
871+
@test !(TwoParams(3im,0im) isa TwoParams{<:Real,<:Number})
872+
@test !(TwoParams(3,"foo") isa TwoParams{<:Real,<:Number})
873+
ftwoparams(::TwoParams) = 1
874+
ftwoparams(::TwoParams{<:Real}) = 2
875+
ftwoparams(::TwoParams{<:Real,<:Real}) = 3
876+
@test ftwoparams(TwoParams('x',3)) == 1
877+
@test ftwoparams(TwoParams(3,'x')) == 2
878+
@test ftwoparams(TwoParams(3,4)) == 3
879+
@test !([TwoParams(3,4)] isa Vector{TwoParams{<:Real,<:Real}})
880+
@test TwoParams{<:Real,<:Real}[TwoParams(3,4)] isa Vector{TwoParams{<:Real,<:Real}}
881+
@test [TwoParams(3,4)] isa Vector{<:TwoParams{<:Real,<:Real}}
882+
@test [TwoParams(3,4)] isa (Vector{TwoParams{T,T}} where T<:Real)
883+
884+
# implicit "contravariant" type parameters:
885+
@test TwoParams{>:Int,<:Number} == (TwoParams{S,T} where S>:Int where T<:Number) ==
886+
(TwoParams{S,<:Number} where S>:Int) == (TwoParams{>:Int,T} where T<:Number)
887+
@test TwoParams(3,0im) isa TwoParams{>:Int,<:Number}
888+
@test TwoParams{Real,Complex}(3,0im) isa TwoParams{>:Int,<:Number}
889+
@test !(TwoParams(3.0,0im) isa TwoParams{>:Int,<:Number})
890+
@test !(TwoParams(3,'x') isa TwoParams{>:Int,<:Number})

0 commit comments

Comments
 (0)