Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lazy broadcasts for reductions / more broadcasting customization docs #30939

Closed
wants to merge 2 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 35 additions & 6 deletions doc/src/manual/interfaces.md
Original file line number Diff line number Diff line change
Expand Up @@ -463,13 +463,18 @@ The [`Base.broadcastable`](@ref) function is called on each argument to broadcas
it to return something different that supports `axes` and indexing. By
default, this is the identity function for all `AbstractArray`s and `Number`s — they already
support `axes` and indexing. For a handful of other types (including but not limited to
types themselves, functions, special singletons like [`missing`](@ref) and [`nothing`](@ref), and dates),
the following),
`Base.broadcastable` returns the argument wrapped in a `Ref` to act as a 0-dimensional
"scalar" for the purposes of broadcasting. Custom types can similarly specialize
`Base.broadcastable` to define their shape, but they should follow the convention that
`collect(Base.broadcastable(x)) == collect(x)`. A notable exception is `AbstractString`;
strings are special-cased to behave as scalars for the purposes of broadcast even though
they are iterable collections of their characters (see [Strings](@ref) for more).
"scalar" for the purposes of broadcasting:
```julia
Base.broadcastable(x::Union{Symbol,AbstractString,Function,UndefInitializer,Nothing,RoundingMode,Missing,Val,Ptr,Regex}) = Ref(x)
Base.broadcastable(::Type{T}) where {T} = Ref{Type{T}}(T)
Base.broadcastable(x) = collect(x)
```
Custom types can similarly specialize `Base.broadcastable` to define their shape, but iterables
should follow the convention that `collect(Base.broadcastable(x)) == collect(x)`. A notable
exception is `AbstractString`; strings are special-cased to behave as scalars for the purposes
of broadcast even though they are iterable collections of their characters (see [Strings](@ref) for more).

The next two steps (selecting the output array and implementation) are dependent upon
determining a single answer for a given set of arguments. Broadcast must take all the varied
Expand Down Expand Up @@ -670,6 +675,30 @@ ways of doing so:
* Iterating over the `CartesianIndices` of the `axes(::Broadcasted)` and using
indexing with the resulting `CartesianIndex` object to compute the result.

#### Example: Preventing materialization for reductions

An instructive example is to permit reductions on broadcasted objects, without
materializing the intermediate results. We can prevent materialization by
```julia
struct lazy end
struct Lazy{T}
arg::T
end
Base.Broadcast.materialize(ell::Lazy) = ell.arg
Base.Broadcast.broadcasted(::Type{lazy}, arg) = Lazy(arg)
```
Now, we can write e.g.
```julia
v = rand(10_000)
w = rand(10_000)
lazyprod = lazy.(v .* w)
dotproduct = sum(lazyprod)
```
This works because `Broadcasted` objects behave like an `Array` in many ways: They are
iterable, have a shape, and appropriate `getindex` to compute elements on demand. However,
they do not have an `eltype`, even if they are correctly inferred, which makes it somewhat
awkward to write type-stable reductions, and is part of the reason that they are not `<:AbstractArray`.

### [Writing binary broadcasting rules](@id writing-binary-broadcasting-rules)

The precedence rules are defined by binary `BroadcastStyle` calls:
Expand Down