Skip to content

Commit a6cc656

Browse files
committed
Documentation update
[ci skip]
1 parent 79ce497 commit a6cc656

File tree

1 file changed

+67
-128
lines changed

1 file changed

+67
-128
lines changed

doc/src/manual/interfaces.md

Lines changed: 67 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,8 @@ V = view(A, [1,2,4], :) # is not strided, as the spacing between rows is not f
449449
| `Base.copy(bc::Broadcasted{DestStyle})` | Custom implementation of `broadcast` |
450450
| `Base.copyto!(dest, bc::Broadcasted{DestStyle})` | Custom implementation of `broadcast!`, specializing on `DestStyle` |
451451
| `Base.copyto!(dest::DestType, bc::Broadcasted{Nothing})` | Custom implementation of `broadcast!`, specializing on `DestType` |
452+
| `Base.Broadcast.make(f, args...)` | Override the default lazy behavior within a fused expression |
453+
| `Base.Broadcast.instantiate(bc::Broadcasted{DestStyle})` | Override the computation of the wrapper's axes and indexers |
452454

453455
[Broadcasting](@ref) is triggered by an explicit call to `broadcast` or `broadcast!`, or implicitly by
454456
"dot" operations like `A .+ b` or `f.(x, y)`. Any object that has [`axes`](@ref) and supports
@@ -461,16 +463,16 @@ in an `Array`. This basic framework is extensible in three major ways:
461463

462464
Not all types support `axes` and indexing, but many are convenient to allow in broadcast.
463465
The [`Base.broadcastable`](@ref) function is called on each argument to broadcast, allowing
464-
it to return something different that supports `axes` and indexing if it does not. By
466+
it to return something different that supports `axes` and indexing. By
465467
default, this is the identity function for all `AbstractArray`s and `Number`s — they already
466468
support `axes` and indexing. For a handful of other types (including but not limited to
467469
types themselves, functions, special singletons like `missing` and `nothing`, and dates),
468470
`Base.broadcastable` returns the argument wrapped in a `Ref` to act as a 0-dimensional
469471
"scalar" for the purposes of broadcasting. Custom types can similarly specialize
470472
`Base.broadcastable` to define their shape, but they should follow the convention that
471-
`collect(Base.broadcastable(x)) == collect(x)`. A notable exception are `AbstractString`s;
472-
they are special-cased to behave as scalars for the purposes of broadcast even though they
473-
are iterable collections of their characters.
473+
`collect(Base.broadcastable(x)) == collect(x)`. A notable exception is `AbstractString`;
474+
strings are special-cased to behave as scalars for the purposes of broadcast even though
475+
they are iterable collections of their characters.
474476

475477
The next two steps (selecting the output array and implementation) are dependent upon
476478
determining a single answer for a given set of arguments. Broadcast must take all the varied
@@ -481,12 +483,11 @@ styles into a single answer — the "destination style".
481483

482484
### Broadcast Styles
483485

484-
`Base.BroadcastStyle` is the abstract type from which all styles are
485-
derived. When used as a function it has two possible forms,
486-
unary (single-argument) and binary.
487-
The unary variant states that you intend to
488-
implement specific broadcasting behavior and/or output type,
489-
and do not wish to rely on the default fallback ([`Broadcast.DefaultArrayStyle`](@ref)).
486+
`Base.BroadcastStyle` is the abstract type from which all broadcast styles are derived. When used as a
487+
function it has two possible forms, unary (single-argument) and binary. The unary variant states
488+
that you intend to implement specific broadcasting behavior and/or output type, and do not wish to
489+
rely on the default fallback [`Broadcast.DefaultArrayStyle`](@ref).
490+
490491
To override these defaults, you can define a custom `BroadcastStyle` for your object:
491492

492493
```julia
@@ -505,29 +506,30 @@ leverage one of the general broadcast wrappers:
505506

506507
When your broadcast operation involves several arguments, individual argument styles get
507508
combined to determine a single `DestStyle` that controls the type of the output container.
508-
For more detail, see [below](@ref writing-binary-broadcasting-rules).
509+
For more details, see [below](@ref writing-binary-broadcasting-rules).
509510

510511
### Selecting an appropriate output array
511512

512-
The actual allocation of the result array is handled by `Base.broadcast_similar`:
513+
The broadcast style is computed for every broadcasting operation to allow for
514+
dispatch and specialization. The actual allocation of the result array is
515+
handled by `Base.broadcast_similar`, using this style as its first argument.
513516

514517
```julia
515518
Base.broadcast_similar(::DestStyle, ::Type{ElType}, inds, bc)
516519
```
517520

518-
`DestStyle` signals the final result from combining the input styles.
519521
The fallback definition is
520522

521523
```julia
522524
broadcast_similar(::DefaultArrayStyle{N}, ::Type{ElType}, inds::Indices{N}, bc) where {N,ElType} =
523525
similar(Array{ElType}, inds)
524526
```
525527

526-
However, if needed you can specialize on any or all of these arguments.
527-
`bc` is the overall `Broadcasted` wrapper, available in case allocation of the output requires
528-
access to some of the inputs. For these purposes, the important field of `Broadcasted` is called
529-
`args`, which stores the inputs as a linked list (a `TupleLL`). `ll.head` extracts the first
530-
element, while `ll.rest` retrieves the remaining list. The list is terminated by a `TupleLLEnd()`.
528+
However, if needed you can specialize on any or all of these arguments. The final argument
529+
`bc` is a lazy representation of a (potentially fused) broadcast operation, a `Broadcasted`
530+
object. For these purposes, the most important fields of the wrapper are
531+
`f` and `args`, describing the function and argument list, respectively. Note that the argument
532+
list can — and often does — include other nested `Broadcasted` wrappers.
531533

532534
For a complete example, let's say you have created a type, `ArrayAndChar`, that stores an
533535
array and a single character:
@@ -564,7 +566,7 @@ end
564566
565567
"`A = find_aac(As)` returns the first ArrayAndChar among the arguments."
566568
find_aac(bc::Base.Broadcast.Broadcasted) = find_aac(bc.args)
567-
find_aac(ll::Base.TupleLL) = find_aac(find_aac(ll.head), ll.rest)
569+
find_aac(args::Tuple) = find_aac(find_aac(args[1]), Base.tail(args))
568570
find_aac(x) = x
569571
find_aac(a::ArrayAndChar, rest) = a
570572
find_aac(::Any, rest) = find_aac(rest)
@@ -588,21 +590,6 @@ julia> a .+ [5,10]
588590
13 14
589591
```
590592

591-
### Customizing the broadcast result type
592-
593-
All `AbstractArray`s support broadcasting in arbitrary combinations with one another, but the
594-
default result (output) type is `Array`. The `Broadcasted` container has a dedicated type parameter
595-
`Broadcasted{DestStyle}` — specifically to allow for dispatch and specialization. It computes
596-
this "broadcast style" by recursively asking every argument for its `Base.BroadcastStyle` and
597-
[combining them together with a promotion-like computation](@ref writing-binary-broadcasting-rules).
598-
599-
`Base.BroadcastStyle` is an abstract type from which all styles are derived. When used as a
600-
function it has two possible forms, unary (single-argument) and binary. The unary variant states
601-
that you intend to implement specific broadcasting behavior and/or output type, and do not wish to
602-
rely on the default fallback ([`Broadcast.Scalar`](@ref) or [`Broadcast.DefaultArrayStyle`](@ref)).
603-
To achieve this, you can define a custom `BroadcastStyle` for your object:
604-
605-
606593
### [Extending broadcast with custom implementations](@id extending-in-place-broadcast)
607594

608595
In general, a broadcast operation is represented by a lazy `Broadcasted` container that holds onto
@@ -618,98 +605,73 @@ it, and then finally copy the realization of the `Broadcasted` object into it wi
618605
`broadcast!` methods similarly construct a transient `Broadcasted` representation of the operation
619606
so they can follow the same codepath. This allows custom array implementations to
620607
provide their own `copyto!` specialization to customize and
621-
optimize broadcasting. In order to get to that point, though, custom arrays must first signal the
622-
fact that they should return a custom array from the broadcast operation.
623-
608+
optimize broadcasting. This is again determined by the computed broadcast style. This is such
609+
an important part of the operation that it is stored as the first type parameter of the
610+
`Broadcasted` type, allowing for dispatch and specialization.
624611

625612
For some types, the machinery to "fuse" operations across nested levels of broadcasting
626-
is not available. In such cases, you may need to evaluate `x .* (x .+ 1)` as if it had been
613+
is not available or could be done more efficiently incrementally. In such cases, you may
614+
need or want to evaluate `x .* (x .+ 1)` as if it had been
627615
written `broadcast(*, x, broadcast(+, x, 1))`, where the inner operation is evaluated before
628-
tackling the outer operation. You can force eager evaluation by defining
616+
tackling the outer operation. This sort of eager operation is directly supported by a bit
617+
of indirection; instead of directly constructing `Broadcasted` objects, Julia lowers the
618+
fused expression `x .* (x .+ 1)` to `Broadcast.make(*, x, Broadcast.make(+, x, 1))`. Now,
619+
by default, `make` just calls the `Broadcasted` constructor to create the lazy representation
620+
of the fused expression tree, but you can choose to override it for a particular combination
621+
of function and arguments.
622+
623+
As an example, the builtin `AbstractRange` objects use this machinery to optimize pieces
624+
of broadcasted expressions that can be eagerly evaluated purely in terms of the start,
625+
step, and length (or stop) instead of computing every single element. Just like all the
626+
other machinery, `make` also computes and exposes the combined broadcast style of its
627+
arguments, so instead of specializing on `make(f, args...)`, you can specialize on
628+
`make(::DestStyle, f, args...)` for any combination of style, function, and arguments.
629+
630+
For example, the following definition supports the negation of ranges:
629631

630632
```julia
631-
is_broadcast_incremental(bc::Broadcasted{DestStyle}) = true
633+
make(::DefaultArrayStyle{1}, ::typeof(-), r::OrdinalRange) = range(-first(r), step=-step(r), length=length(r))
632634
```
633-
In such cases you need to supply specific methods
634-
```julia
635-
broadcast(f, arg1::ArgType1, ...)
636-
```
637-
for all operations that might be triggered, otherwise the result will be circular and a
638-
`StackOverflowError` will result.
639635

640-
Your definition of `is_broadcast_incremental` can be more sophisticated, if necessary;
641-
in particular, you can examine the types of `bc.args` if you need to make a more nuanced decision.
642-
As an example, here is the implementation that allows Julia to return `AbstractRange` objects
643-
from broadcasting:
636+
### [Extending in-place broadcasting](@id extending-in-place-broadcast)
644637

645-
```julia
646-
is_broadcast_incremental(bc::Broadcasted{DefaultArrayStyle{1}}) = maybe_range_safe(bc)
647-
648-
# Support incremental evaluation only for 1- or 2-argument broadcasting
649-
# Broadcast.broadcast_all(f_filter, arg_filter, bc) is a function that checks all
650-
# inputs to a nested broadcasting operation, ensuring that the function `f` and
651-
# arguments return `true` for their respective filter functions.
652-
const Args1{T} = TupleLL{T,TupleLLEnd}
653-
const Args2{S,T} = TupleLL{S,TupleLL{T,TupleLLEnd}}
654-
@inline maybe_range_safe(bc::Broadcasted{Style}) where {Style<:AbstractArrayStyle{1}} =
655-
Broadcast.broadcast_all(maybe_range_safe_f, maybe_range_safe_arg, bc) && bc.args isa Union{Args1,Args2}
656-
657-
# Support incremental evaluation only for operations that might return an AbstractRange
658-
maybe_range_safe_f(::typeof(+)) = true
659-
maybe_range_safe_f(::typeof(-)) = true
660-
maybe_range_safe_f(::typeof(*)) = true
661-
maybe_range_safe_f(::typeof(/)) = true
662-
maybe_range_safe_f(::typeof(\)) = true
663-
maybe_range_safe_f(f) = false
664-
665-
maybe_range_safe_arg(::AbstractRange) = true
666-
maybe_range_safe_arg(::Number) = true
667-
maybe_range_safe_arg(x) = false
668-
```
669-
670-
It's then necessary to write `broadcast` methods for all 1- and 2-argument versions of operations
671-
involving at least one `AbstractRange` and the supported operations `+`, `-`, `*`, `/`, and `\`.
672-
For example,
638+
In-place broadcasting can be supported by defining the appropriate `copyto!(dest, bc::Broadcasted)`
639+
method. Because you might want to specialize either on `dest` or the specific subtype of `bc`,
640+
to avoid ambiguities between packages we recommend the following convention.
673641

642+
If you wish to specialize on a particular style `DestStyle`, define a method for
674643
```julia
675-
broadcast(::typeof(-), r::OrdinalRange) = range(-first(r), -step(r), length(r))
644+
copyto!(dest, bc::Broadcasted{DestStyle})
676645
```
677-
to define negation of a range.
678-
679-
Extending `broadcast!` (in-place broadcast) should be done with care, as it is easy to introduce
680-
ambiguities between packages. To avoid these ambiguities, we adhere to the following conventions.
646+
Optionally, with this form you can also specialize on the type of `dest`.
681647

682-
First, if you want to specialize on the destination type, say `DestType`, then you should
683-
define a method with the following signature:
648+
If instead you want to specialize on the destination type `DestType` without specializing
649+
on `DestStyle`, then you should define a method with the following signature:
684650

685651
```julia
686-
broadcast!(f, dest::DestType, ::Nothing, As...)
652+
copyto!(dest::DestType, bc::Broadcasted{Nothing})
687653
```
688654

689-
Note that no bounds should be placed on the types of `f` and `As...`.
690-
691-
Second, if specialized `broadcast!` behavior is desired depending on the input types,
692-
you should write [binary broadcasting rules](@ref writing-binary-broadcasting-rules) to
693-
determine a custom `BroadcastStyle` given the input types, say `MyBroadcastStyle`, and you should define a method with the following
694-
signature:
695-
696-
```julia
697-
broadcast!(f, dest, ::MyBroadcastStyle, As...)
698-
```
655+
This leverages a fallback implementation of `copyto!` that converts the wrapper into a
656+
`Broadcasted{Nothing}`. Consequently, specializing on `DestType` has lower precedence than
657+
methods that specialize on `DestStyle`.
699658

700-
Note the lack of bounds on `f`, `dest`, and `As...`.
659+
Similarly, you can completely override out-of-place broadcasting with a `copy(::Broadcasted)`
660+
method.
701661

702-
Third, simultaneously specializing on both the type of `dest` and the `BroadcastStyle` is fine. In this case,
703-
it is also allowed to specialize on the types of the source arguments (`As...`). For example, these method signatures are OK:
662+
#### Working with `Broadcasted` objects
704663

705-
```julia
706-
broadcast!(f, dest::DestType, ::MyBroadcastStyle, As...)
707-
broadcast!(f, dest::DestType, ::MyBroadcastStyle, As::AbstractArray...)
708-
broadcast!(f, dest::DestType, ::Broadcast.DefaultArrayStyle{0}, As::Number...)
709-
```
664+
In order to implement such a `copy` or `copyto!`, method, of course, you must
665+
work with the `Broadcasted` wrapper to compute each element. There are two main
666+
ways of doing so:
710667

668+
* `Broadcast.flatten` recomputes the potentially nested operation into a single
669+
function and flat list of arguments. You are responsible for implementing the
670+
broadcasting shape rules yourself, but this may be helpful in limited situations.
671+
* Iterating over the `CartesianIndices` of the `axes(::Broadcasted)` and using
672+
indexing with the resulting `CartesianIndex` object to compute the result.
711673

712-
#### [Writing binary broadcasting rules](@id writing-binary-broadcasting-rules)
674+
### [Writing binary broadcasting rules](@id writing-binary-broadcasting-rules)
713675

714676
The precedence rules are defined by binary `BroadcastStyle` calls:
715677

@@ -772,26 +734,3 @@ yields another `SparseVecStyle`, that its combination with a 2-dimensional array
772734
yields a `SparseMatStyle`, and anything of higher dimensionality falls back to the dense arbitrary-dimensional framework.
773735
These rules allow broadcasting to keep the sparse representation for operations that result
774736
in one or two dimensional outputs, but produce an `Array` for any other dimensionality.
775-
776-
### [Extending in-place broadcasting](@id extending-in-place-broadcast)
777-
778-
In-place broadcasting can be supported by defining the appropriate `copyto!(dest, bc::Broadcasted)`
779-
method. Because you might want to specialize either on `dest` or the specific subtype of `bc`,
780-
to avoid ambiguities between packages we recommend the following convention.
781-
782-
If you wish to specialize on a particular style `DestStyle`, define a method for
783-
```julia
784-
copyto!(dest, bc::Broadcasted{DestStyle})
785-
```
786-
Optionally, with this form you can also specialize on the type of `dest`.
787-
788-
If instead you want to specialize on the destination type `DestType` without specializing
789-
on `DestStyle`, then you should define a method with the following signature:
790-
791-
```julia
792-
copyto!(dest::DestType, bc::Broadcasted{Nothing})
793-
```
794-
795-
This leverages a fallback implementation of `copyto!` that converts the wrapper into a
796-
`Broadcasted{Nothing}`. Consequently, specializing on `DestType` has lower precedence than
797-
methods that specialize on `DestStyle`.

0 commit comments

Comments
 (0)