diff --git a/doc/src/manual/interfaces.md b/doc/src/manual/interfaces.md index d5a53e93d6843..33ea510f8828d 100644 --- a/doc/src/manual/interfaces.md +++ b/doc/src/manual/interfaces.md @@ -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 @@ -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: