From 9d98937689c4a7d5b0cdfc6ccaa0a37f138f95cb Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Thu, 2 Feb 2017 15:41:27 -0500 Subject: [PATCH 1/6] syntactic sugar Foo{<:Bar} for Foo{T} where T<:Bar (closes #6984) --- NEWS.md | 2 ++ doc/src/manual/types.md | 8 ++++++-- src/julia-syntax.scm | 22 +++++++++++++++++++++- test/subtype.jl | 15 +++++++++++++++ 4 files changed, 44 insertions(+), 3 deletions(-) diff --git a/NEWS.md b/NEWS.md index cff00ac489416..c3a52fdf30ccd 100644 --- a/NEWS.md +++ b/NEWS.md @@ -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`. * Much more accurate subtype and type intersection algorithms. Method sorting and identification of equivalent and ambiguous methods are improved as a result. diff --git a/doc/src/manual/types.md b/doc/src/manual/types.md index e4289d906a1ca..3173837c4131e 100644 --- a/doc/src/manual/types.md +++ b/doc/src/manual/types.md @@ -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 @@ -990,6 +993,7 @@ 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`). diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index d6a81d8aa014c..2ef7089948fb1 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -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. (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) (eq? (cadr p) '|<:|)) + (let ((T (gensy))) + (extract (cdr params) (cons T newparams) (cons (list '|<:| 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 @@ -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) diff --git a/test/subtype.jl b/test/subtype.jl index bd15035b82e5c..fe243b6bb21c4 100644 --- a/test/subtype.jl +++ b/test/subtype.jl @@ -861,3 +861,18 @@ f18348{T<:Any}(::Type{T}, x::T) = 2 # Issue #12721 f12721{T<:Type{Int}}(::T) = true @test_throws MethodError f12721(Float64) + +# implicit 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 From 1ae740401788dc94833f388cf03d133c2a2c1dca Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Thu, 2 Feb 2017 16:08:20 -0500 Subject: [PATCH 2/6] add nesting test --- test/subtype.jl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/subtype.jl b/test/subtype.jl index fe243b6bb21c4..d96194505120a 100644 --- a/test/subtype.jl +++ b/test/subtype.jl @@ -876,3 +876,6 @@ 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{T,T}} where T<:Real) From 6b7249f13e42988dbf1a823b513c673ed11f627a Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Thu, 2 Feb 2017 16:08:54 -0500 Subject: [PATCH 3/6] add PR number --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index c3a52fdf30ccd..ee7d92b69428d 100644 --- a/NEWS.md +++ b/NEWS.md @@ -15,7 +15,7 @@ New language features 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`. + `Vector{T} where T<:Real` ([#20414]). * Much more accurate subtype and type intersection algorithms. Method sorting and identification of equivalent and ambiguous methods are improved as a result. From f5381032e5e62d1f6539ae4dbd8f4b937873c4d5 Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Thu, 2 Feb 2017 16:26:16 -0500 Subject: [PATCH 4/6] add implicit contravariant parameters --- NEWS.md | 2 +- doc/src/manual/types.md | 17 ++++++++++++++++- src/julia-syntax.scm | 8 ++++---- test/subtype.jl | 10 +++++++++- 4 files changed, 30 insertions(+), 7 deletions(-) diff --git a/NEWS.md b/NEWS.md index ee7d92b69428d..848a28127a9fa 100644 --- a/NEWS.md +++ b/NEWS.md @@ -15,7 +15,7 @@ New language features 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` ([#20414]). + `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. diff --git a/doc/src/manual/types.md b/doc/src/manual/types.md index 3173837c4131e..f4edd7c6a5eec 100644 --- a/doc/src/manual/types.md +++ b/doc/src/manual/types.md @@ -714,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, +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: @@ -743,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 @@ -997,7 +1011,8 @@ The syntax `Array{<:Integer}` is a convenient shorthand for `Array{T} where T<:I 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 diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index 2ef7089948fb1..4b12960b9b880 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -1764,16 +1764,16 @@ (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. (This implements -; the syntactic sugar Foo{<:Bar} --> Foo{T} where T<:Bar.) +; 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) (eq? (cadr p) '|<:|)) + (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 '|<:| T (caddr p)) whereparams))) + (extract (cdr params) (cons T newparams) (cons (list (cadr p) T (caddr p)) whereparams))) (extract (cdr params) (cons p newparams) whereparams))))) (extract (cddr e) '() '())) diff --git a/test/subtype.jl b/test/subtype.jl index d96194505120a..79fbb043a7b7d 100644 --- a/test/subtype.jl +++ b/test/subtype.jl @@ -862,7 +862,7 @@ f18348{T<:Any}(::Type{T}, x::T) = 2 f12721{T<:Type{Int}}(::T) = true @test_throws MethodError f12721(Float64) -# implicit type parameters: +# 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) @@ -879,3 +879,11 @@ ftwoparams(::TwoParams{<:Real,<:Real}) = 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{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}) From b1ef60e5339189ae2fcb975db29531b620b7db58 Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Thu, 2 Feb 2017 16:48:33 -0500 Subject: [PATCH 5/6] another nesting test --- test/subtype.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/test/subtype.jl b/test/subtype.jl index 79fbb043a7b7d..4ac5347df88d6 100644 --- a/test/subtype.jl +++ b/test/subtype.jl @@ -878,6 +878,7 @@ ftwoparams(::TwoParams{<:Real,<:Real}) = 3 @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: From 272ca05d1aaa8e2ad57750df1c5a5c45388d912e Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Thu, 2 Feb 2017 18:02:14 -0500 Subject: [PATCH 6/6] add wikipedia link to co/contravariant types --- doc/src/manual/types.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/src/manual/types.md b/doc/src/manual/types.md index f4edd7c6a5eec..e5bd952cc2e89 100644 --- a/doc/src/manual/types.md +++ b/doc/src/manual/types.md @@ -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: