Skip to content

Commit

Permalink
docs for new broadcasting dot-operator behavior
Browse files Browse the repository at this point in the history
  • Loading branch information
stevengj committed Dec 13, 2016
1 parent 0ef3d81 commit 612bfdd
Show file tree
Hide file tree
Showing 6 changed files with 125 additions and 40 deletions.
15 changes: 14 additions & 1 deletion NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ New language features
Language changes
----------------

* Multiline and singleline nonstandard command literals have been added. A
* Multi-line and single-line nonstandard command literals have been added. A
nonstandard command literal is like a nonstandard string literal, but the
syntax uses backquotes (``` ` ```) instead of double quotes, and the
resulting macro called is suffixed with `_cmd`. For instance, the syntax
Expand All @@ -17,6 +17,11 @@ Language changes
module. For instance, `Base.r"x"` is now parsed as `Base.@r_str "x"`.
Previously, this syntax parsed as an implicit multiplication. ([#18690])

* For every binary operator ``, `a .⨳ b` is now automatically equivalent to
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])

Breaking changes
----------------

Expand All @@ -34,6 +39,14 @@ This section lists changes that do not have deprecation warnings.
* `broadcast` now handles tuples, and treats any argument that is not a tuple
or an array as a "scalar" ([#16986]).

* `broadcast` now produces a `BitArray` instead of `Array{Bool}` for
functions yielding a boolean result. If you want `Array{Bool}`, use
`broadcast!` or `.=` ([#17623]).

* Operations like `.+` and `.*` on `Range` objects are now generic
`broadcast` calls (see above) and produce an `Array`. If you want
a `Range` result, use `+` and `*`, etcetera ([#17623]).

Library improvements
--------------------

Expand Down
37 changes: 22 additions & 15 deletions doc/src/manual/arrays.md
Original file line number Diff line number Diff line change
Expand Up @@ -402,29 +402,33 @@ specify this trait, the default value `LinearSlow()` is used.

### Vectorized Operators and Functions

The following operators are supported for arrays. The dot version of a binary operator should
be used for elementwise operations.
The following operators are supported for arrays. Also, *every* binary
operator supports a [dot version](@ref man-dot-operators) that can be
applied to arrays (and combinations of arrays and scalars) as a
[fused broadcasting operation](@ref man-vectorized). (For comparison
operations like `<`, *only* the `.<` version is applicable to arrays.)

1. Unary arithmetic -- `-`, `+`, `!`
2. Binary arithmetic -- `+`, `-`, `*`, `.*`, `/`, `./`, `\`, `.\`, `^`, `.^`, `div`, `mod`
3. Comparison -- `.==`, `.!=`, `.<`, `.<=`, `.>`, `.>=`
2. Binary arithmetic -- `+`, `-`, `*`, `/`, `\`, `^`, `.^`, `div`, `mod`
3. Comparison -- `==`, `!=`, `` ([`isapprox`](@ref)), ``
4. Unary Boolean or bitwise -- `~`
5. Binary Boolean or bitwise -- `&`, `|`, `$`

Some operators without dots operate elementwise anyway when one argument is a scalar. These operators
are `*`, `+`, `-`, and the bitwise operators. The operators `/` and `\` operate elementwise when
Some operators without dots operate elementwise anyway when one argument is a scalar:
`*`, `+`, `-`, and the bitwise operators. The operators `/` and `\` operate elementwise when
the denominator is a scalar.

Note that comparisons such as `==` operate on whole arrays, giving a single boolean answer. Use
dot operators for elementwise comparisons.
dot operators like `.==` for elementwise comparisons.

To enable convenient vectorization of mathematical and other operations, Julia provides the compact
syntax `f.(args...)`, e.g. `sin.(x)` or `min.(x,y)`, for elementwise operations over arrays or
mixtures of arrays and scalars (a [`broadcast()`](@ref) operation). See [Dot Syntax for Vectorizing Functions](@ref).
To enable convenient vectorization of mathematical and other operations, Julia [provides the compact
syntax](@ref man-vectorized) `f.(args...)`, e.g. `sin.(x)` or `min.(x,y)`, for elementwise operations over arrays or mixtures of arrays and scalars (a [Broadcasting](@ref) operation); these
have the additional advantage of "fusing" into a single loop when combined with
dot operators and other dot calls.

Note that there is a difference between `max.(a,b)`, which `broadcast`s [`max()`](@ref) elementwise
over `a` and `b`, and `maximum(a)`, which finds the largest value within `a`. The same statements
hold for `min.(a,b)` and `minimum(a)`.
over `a` and `b`, and `maximum(a)`, which finds the largest value within `a`. The same relationship
holds for `min.(a,b)` and `minimum(a)`.

### Broadcasting

Expand Down Expand Up @@ -461,11 +465,14 @@ julia> broadcast(+, a, b)
1.73659 0.873631
```

Elementwise operators such as `.+` and `.*` perform broadcasting if necessary. There is also a
[`broadcast!()`](@ref) function to specify an explicit destination, and [`broadcast_getindex()`](@ref)
[Dotted operators](@ref man-dot-operators) such as `.+` and `.*` are equivalent
to `broadcast` calls (except that they fuse, as described below). There is also a
[`broadcast!()`](@ref) function to specify an explicit destination (which can also
be accessed in a fusing fashion by `.=` assignment), and functions [`broadcast_getindex()`](@ref)
and [`broadcast_setindex!()`](@ref) that broadcast the indices before indexing. Moreover, `f.(args...)`
is equivalent to `broadcast(f, args...)`, providing a convenient syntax to broadcast any function
([Dot Syntax for Vectorizing Functions](@ref)).
([dot syntax](@ref man-vectorized)), with the added benefit that nested "dot calls"
*fuse* into a single `broadcast` loop.

Additionally, [`broadcast()`](@ref) is not limited to arrays (see the function documentation),
it also handles tuples and treats any argument that is not an array or a tuple as a "scalar".
Expand Down
11 changes: 5 additions & 6 deletions doc/src/manual/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -554,7 +554,7 @@ normally or threw an exception. (The `try/finally` construct will be described i
With the `do` block syntax, it helps to check the documentation or implementation to know how
the arguments of the user function are initialized.
## Dot Syntax for Vectorizing Functions
## [Dot Syntax for Vectorizing Functions](@id man-vectorized)
In technical-computing languages, it is common to have "vectorized" versions of functions, which
simply apply a given function `f(x)` to each element of an array `A` to yield a new array via
Expand Down Expand Up @@ -595,10 +595,10 @@ overwriting `X` with `sin.(Y)` in-place. If the left-hand side is an array-index
e.g. `X[2:end] .= sin.(Y)`, then it translates to `broadcast!` on a `view`, e.g. `broadcast!(sin, view(X, 2:endof(X)), Y)`,
so that the left-hand side is updated in-place.
(In future versions of Julia, operators like `.*` will also be handled with the same mechanism:
they will be equivalent to `broadcast` calls and will be fused with other nested "dot" calls.
`X .+= Y` is equivalent to `X .= X .+ Y` and will eventually result in a fused in-place assignment.
Similarly for `.*=` etcetera.)
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).
## Further Reading
Expand All @@ -607,4 +607,3 @@ a sophisticated type system and allows multiple dispatch on argument types. None
given here provide any type annotations on their arguments, meaning that they are applicable to
all types of arguments. The type system is described in [Types](@ref man-types) and defining a function
in terms of methods chosen by multiple dispatch on run-time argument types is described in [Methods](@ref).
53 changes: 37 additions & 16 deletions doc/src/manual/mathematical-operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,34 @@ The updating versions of all the binary arithmetic and bitwise operators are:
true
```

## [Elementwise "dot" operators](@id man-dot-operators)

For *every* binary operation like `^`, there is a corresponding
"dot" operation `.^` that is *automatically* defined
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 being equivalent to the elementwise operation `[1^3, 2^3, 3^3]`.

More specifically, `a .^ b` is parsed as the [vectorized call](@ref man-vectorized)
`(^).(a,b)`, which performs a [broadcast](@ref Broadcasting) operation:
it can combine arrays and scalars, arrays of the same size (performing
the operation elementwise), and even arrays of different shapes (e.g.
combining row and column vectors to produce a matrix). Moreover, like
all vectorized calls via the "dot syntax," these "dot operators" are
*fusing*. For example, if you compute `2 .* A.^2 .+ sin.(A)` for an
array `A`, it performs a *single* loop over `A`, computing `2a^2 + sin(a)`
for each element of `A`.

Furthermore, "dotted" updating operators like `a .+= b` are parsed
as `a .= a .+ b`, where `.=` is a fused *in-place* assignment operation
(see the [dot syntax documentation](@ref man-vectorized)).

Note the dot syntax is also applicable to user-defined operators.
For example, if you define `⊗(A,B) = kron(A,B)` to give a convenient
infix syntax `A ⊗ B` for Kronecker products ([`kron`](@ref)), then
`[A,B] .⊗ [C,D]` will compute `[A⊗C, B⊗D]` with no additional coding.

## Numeric Comparisons

Standard comparison operations are defined for all the primitive numeric types:
Expand Down Expand Up @@ -265,13 +293,6 @@ Chaining comparisons is often quite convenient in numerical code. Chained compar
which allows them to work on arrays. For example, `0 .< A .< 1` gives a boolean array whose entries
are true where the corresponding elements of `A` are between 0 and 1.

The operator [`.<`](@ref) is intended for array objects; the operation `A .< B` is valid only
if `A` and `B` have the same dimensions. The operator returns an array with boolean entries and
with the same dimensions as `A` and `B`. Such operators are called *elementwise*; Julia offers
a suite of elementwise operators: [`.*`](@ref), [`.+`](@ref), etc. Some of the elementwise operators
can take a scalar operand such as the example `0 .< A .< 1` in the preceding paragraph. This notation
means that the scalar operand should be replicated for each entry of the array.

Note the evaluation behavior of chained comparisons:

```julia
Expand Down Expand Up @@ -303,15 +324,15 @@ Julia applies the following order of operations, from highest precedence to lowe
| Category | Operators |
|:-------------- |:------------------------------------------------------------------------------------------------- |
| Syntax | `.` followed by `::` |
| Exponentiation | `^` and its elementwise equivalent `.^` |
| Fractions | `//` and `.//` |
| Multiplication | `* / % & \` and `.* ./ .% .\` |
| Bitshifts | `<< >> >>>` and `.<< .>> .>>>` |
| Addition | `+ - \| ⊻` and `.+ .-` |
| Exponentiation | `^` |
| Fractions | `//` |
| Multiplication | `* / % & \` |
| Bitshifts | `<< >> >>>` |
| Addition | `+ - \| ⊻` |
| Syntax | `: ..` followed by `\|>` |
| Comparisons | `> < >= <= == === != !== <:` and `.> .< .>= .<= .== .!=` |
| Comparisons | `> < >= <= == === != !== <:` |
| Control flow | `&&` followed by `\|\|` followed by `?` |
| Assignments | `= += -= *= /= //= \= ^= ÷= %= \|= &= ⊻= <<= >>= >>>=` and `.+= .-= .*= ./= .//= .\= .^= .÷= .%=` |
| Assignments | `= += -= *= /= //= \= ^= ÷= %= \|= &= ⊻= <<= >>= >>>=` |

### Elementary Functions

Expand All @@ -321,8 +342,8 @@ including integers, floating-point numbers, rationals, and complexes, wherever s
make sense.

Moreover, these functions (like any Julia function) can be applied in "vectorized" fashion to
arrays and other collections with the syntax `f.(A)`, e.g. `sin.(A)` will compute the elementwise
sine of each element of an array `A`. See [Dot Syntax for Vectorizing Functions](@ref).
arrays and other collections with the [dot syntax](@ref man-vectorized) `f.(A)`,
e.g. `sin.(A)` will compute the elementwise sine of each element of an array `A`.

## Numerical Conversions

Expand Down
47 changes: 46 additions & 1 deletion doc/src/manual/performance-tips.md
Original file line number Diff line number Diff line change
Expand Up @@ -863,7 +863,52 @@ type from an algorithm. In the example above, we could have passed a `SubArray`
Taken to its extreme, pre-allocation can make your code uglier, so performance measurements and
some judgment may be required. However, for "vectorized" (element-wise) functions, the convenient
syntax `x .= f.(y)` can be used for in-place operations with fused loops and no temporary arrays
([Dot Syntax for Vectorizing Functions](@ref)).
(see the [dot syntax for vectorizing functions](@ref man-vectorized)).

## More dots: Fuse vectorized operations

Julia has a special [dot syntax](@ref man-vectorized) that converts
any scalar function into a "vectorized" function call, and any operator
into a "vectorized" operator, with the special property that nested
"dot calls" are *fusing*: they are combined at the syntax level into
a single loop, without allocating temporary arrays. If you use `.=` and
similar assignment operators, the result can also be stored in-place
in a pre-allocated array (see above).

In a linear-algebra context, this means that even though operations like
`vector + vector` and `vector * scalar` are defined, it can be advantageous
to instead use `vector .+ vector` and `vector .* scalar` because the
resulting loops can be fused with surrounding computations. For example,
consider the two functions:

```julia
f(x) = 3 * x.^2 + 4 * x + 7 * x.^3
fdot(x) = 3 .* x.^2 .+ 4 .* x .+ 7 .* x.^3
```

Both `f` and `fdot` compute the same thing. However, `fdot` is
significantly faster when applied to an array:

```julia
julia> x = rand(10^6);

julia> @time f(x);
0.020244 seconds (26 allocations: 53.407 MB, 35.88% gc time)

julia> @time fdot(x);
0.004579 seconds (10 allocations: 7.630 MB)

julia> @time f.(x);
0.004391 seconds (35 allocations: 7.631 MB)
```

That is, `fdot(x)` is more than four times faster and allocates 1/7 the
memory of `f(x)`, because each `*` and `+` operation in `f(x)` allocates
a new temporary array and executes in a separate loop. (Of course,
if you just do `f.(x)` then it is as fast as `fdot(x)` in this
example, but in many contexts it is more convenient to just sprinkle
some dots in your expressions rather than defining a separate function
for each vectorized operation.)

## Avoid string interpolation for I/O

Expand Down
2 changes: 1 addition & 1 deletion doc/src/stdlib/punctuation.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ Extended documentation for mathematical symbols & functions is [here](@ref math-
| `''` | delimit character literals |
| ``` ` ` ``` | delimit external process (command) specifications |
| `...` | splice arguments into a function call or declare a varargs function or type |
| `.` | access named fields in objects or names inside modules, also prefixes elementwise operators |
| `.` | access named fields in objects/modules, also prefixes elementwise operator/function calls |
| `a:b` | range a, a+1, a+2, ..., b |
| `a:s:b` | range a, a+s, a+2s, ..., b |
| `:` | index an entire dimension (1:end) |
Expand Down

0 comments on commit 612bfdd

Please sign in to comment.