diff --git a/NEWS.md b/NEWS.md index dba780a656031..cff00ac489416 100644 --- a/NEWS.md +++ b/NEWS.md @@ -34,6 +34,7 @@ Language changes the `broadcast` call `(⨳).(a, b)`. Hence, one no longer defines methods for `.*` etcetera. This also means that "dot operations" automatically fuse into a single loop, along with other dot calls `f.(x)`. ([#17623]) + Similarly for unary operators ([#20249]). * Newly defined methods are no longer callable from the same dynamic runtime scope they were defined in ([#17057]). diff --git a/doc/src/manual/functions.md b/doc/src/manual/functions.md index 004c2d14340b6..f44d4e30f0d86 100644 --- a/doc/src/manual/functions.md +++ b/doc/src/manual/functions.md @@ -660,7 +660,7 @@ julia> @. X = sin(cos(Y)) # equivalent to X .= sin.(cos.(Y)) -0.608083 ``` -Operators like `.*` are handled with the same mechanism: +Binary (or unary) operators like `.+` are handled with the same mechanism: they are equivalent to `broadcast` calls and are fused with other nested "dot" calls. `X .+= Y` etcetera is equivalent to `X .= X .+ Y` and results in a fused in-place assignment; see also [dot operators](@ref man-dot-operators). diff --git a/doc/src/manual/mathematical-operations.md b/doc/src/manual/mathematical-operations.md index 580664a24d710..8e4eea9bbbc38 100644 --- a/doc/src/manual/mathematical-operations.md +++ b/doc/src/manual/mathematical-operations.md @@ -135,7 +135,9 @@ to perform `^` element-by-element on arrays. For example, `[1,2,3] ^ 3` is not defined, since there is no standard mathematical meaning to "cubing" an array, but `[1,2,3] .^ 3` is defined as computing the elementwise -(or "vectorized") result `[1^3, 2^3, 3^3]`. +(or "vectorized") result `[1^3, 2^3, 3^3]`. Similarly for unary +operators like `!` or `√`, there is a corresponding `.√` that +applies the operator elementwise. ```jldoctest julia> [1,2,3] .^ 3 diff --git a/src/julia-parser.scm b/src/julia-parser.scm index df351deb5171f..43dd546c4b4b7 100644 --- a/src/julia-parser.scm +++ b/src/julia-parser.scm @@ -65,10 +65,11 @@ t)) (define (operator-precedence op) (get prec-table op 0)) -(define unary-ops '(+ - ! ¬ ~ |<:| |>:| √ ∛ ∜)) +(define unary-ops (append! '(|<:| |>:| ~) + (add-dots '(+ - ! ¬ √ ∛ ∜)))) ; operators that are both unary and binary -(define unary-and-binary-ops '(+ - $ & ~)) +(define unary-and-binary-ops '(+ - $ & ~ |.+| |.-|)) ; operators that are special forms, not function names (define syntactic-operators @@ -93,9 +94,9 @@ (define operators (filter (lambda (x) (not (is-word-operator? x))) - (list* '~ '! '¬ '-> '√ '∛ '∜ ctrans-op trans-op vararg-op - (delete-duplicates - (apply append (map eval prec-names)))))) + (delete-duplicates + (list* '-> ctrans-op trans-op vararg-op + (append unary-ops (apply append (map eval prec-names))))))) (define op-chars (delete-duplicates @@ -200,7 +201,7 @@ (loop newop (peek-char port))) str)) str)))) - (if (or (equal? str "--") (equal? str ".!")) + (if (equal? str "--") (error (string "invalid operator \"" str "\""))) (string->symbol str)))) diff --git a/test/broadcast.jl b/test/broadcast.jl index 83f74b7fe99dc..cf7c105343160 100644 --- a/test/broadcast.jl +++ b/test/broadcast.jl @@ -327,6 +327,11 @@ let g = Int[], ⊕ = (a,b) -> let c=a+2b; push!(g, c); c; end @test g == [21,221,24,424,27,627] # test for loop fusion end +# Fused unary operators +@test .√[3,4,5] == sqrt.([3,4,5]) +@test .![true, true, false] == [false, false, true] +@test .-[1,2,3] == -[1,2,3] == .+[-1,-2,-3] == [-1,-2,-3] + # PR 16988 @test Base.promote_op(+, Bool) === Int @test isa(broadcast(+, [true]), Array{Int,1}) diff --git a/test/nullable.jl b/test/nullable.jl index 025522312427f..cf103f2e203e6 100644 --- a/test/nullable.jl +++ b/test/nullable.jl @@ -467,10 +467,10 @@ sqr(x) = x^2 @test Nullable(2) .^ Nullable{Int}() |> isnull_oftype(Int) # multi-arg broadcast -@test Nullable(1) .+ Nullable(1) .+ Nullable(1) .+ Nullable(1) .+ Nullable(1) .+ - Nullable(1) === Nullable(6) -@test Nullable(1) .+ Nullable(1) .+ Nullable(1) .+ Nullable{Int}() .+ - Nullable(1) .+ Nullable(1) |> isnull_oftype(Int) +@test (Nullable(1) .+ Nullable(1) .+ Nullable(1) .+ Nullable(1) .+ Nullable(1) .+ + Nullable(1) === Nullable(6)) +@test (Nullable(1) .+ Nullable(1) .+ Nullable(1) .+ Nullable{Int}() .+ + Nullable(1) .+ Nullable(1) |> isnull_oftype(Int)) # these are not inferrable because there are too many arguments us = map(Nullable, 1:20)