# This file is a part of Julia. License is MIT: https://julialang.org/license module Broadcast using Base.Cartesian using Base: linearindices, tail, OneTo, to_shape, _msk_end, unsafe_bitgetindex, bitcache_chunks, bitcache_size, dumpbitcache, nullable_returntype, null_safe_op, hasvalue, isoperator import Base: broadcast, broadcast! export broadcast_getindex, broadcast_setindex!, dotview, @__dot__ const ScalarType = Union{Type{Any}, Type{Nullable}} ## Broadcasting utilities ## # fallbacks for some special cases @inline broadcast(f, x::Number...) = f(x...) @inline broadcast(f, t::NTuple{N,Any}, ts::Vararg{NTuple{N,Any}}) where {N} = map(f, t, ts...) broadcast!(::typeof(identity), x::Array{T,N}, y::Array{S,N}) where {T,S,N} = size(x) == size(y) ? copy!(x, y) : broadcast_c!(identity, Array, Array, x, y) # special cases for "X .= ..." (broadcast!) assignments broadcast!(::typeof(identity), X::AbstractArray, x::Number) = fill!(X, x) broadcast!(f, X::AbstractArray, x::Number...) = (@inbounds for I in eachindex(X); X[I] = f(x...); end; X) # logic for deciding the resulting container type _containertype(::Type) = Any _containertype(::Type{<:Ptr}) = Any _containertype(::Type{<:Tuple}) = Tuple _containertype(::Type{<:Ref}) = Array _containertype(::Type{<:AbstractArray}) = Array _containertype(::Type{<:Nullable}) = Nullable containertype(x) = _containertype(typeof(x)) containertype(ct1, ct2) = promote_containertype(containertype(ct1), containertype(ct2)) @inline containertype(ct1, ct2, cts...) = promote_containertype(containertype(ct1), containertype(ct2, cts...)) promote_containertype(::Type{Array}, ::Type{Array}) = Array promote_containertype(::Type{Array}, ct) = Array promote_containertype(ct, ::Type{Array}) = Array promote_containertype(::Type{Tuple}, ::ScalarType) = Tuple promote_containertype(::ScalarType, ::Type{Tuple}) = Tuple promote_containertype(::Type{Any}, ::Type{Nullable}) = Nullable promote_containertype(::Type{Nullable}, ::Type{Any}) = Nullable promote_containertype(::Type{T}, ::Type{T}) where {T} = T ## Calculate the broadcast indices of the arguments, or error if incompatible # array inputs broadcast_indices() = () broadcast_indices(A) = broadcast_indices(containertype(A), A) broadcast_indices(::ScalarType, A) = () broadcast_indices(::Type{Tuple}, A) = (OneTo(length(A)),) broadcast_indices(::Type{Array}, A::Ref) = () broadcast_indices(::Type{Array}, A) = indices(A) @inline broadcast_indices(A, B...) = broadcast_shape((), broadcast_indices(A), map(broadcast_indices, B)...) # shape (i.e., tuple-of-indices) inputs broadcast_shape(shape::Tuple) = shape @inline broadcast_shape(shape::Tuple, shape1::Tuple, shapes::Tuple...) = broadcast_shape(_bcs((), shape, shape1), shapes...) # _bcs consolidates two shapes into a single output shape _bcs(out, ::Tuple{}, ::Tuple{}) = out @inline _bcs(out, ::Tuple{}, newshape) = _bcs((out..., newshape[1]), (), tail(newshape)) @inline _bcs(out, shape, ::Tuple{}) = _bcs((out..., shape[1]), tail(shape), ()) @inline function _bcs(out, shape, newshape) newout = _bcs1(shape[1], newshape[1]) _bcs((out..., newout), tail(shape), tail(newshape)) end # _bcs1 handles the logic for a single dimension _bcs1(a::Integer, b::Integer) = a == 1 ? b : (b == 1 ? a : (a == b ? a : throw(DimensionMismatch("arrays could not be broadcast to a common size")))) _bcs1(a::Integer, b) = a == 1 ? b : (first(b) == 1 && last(b) == a ? b : throw(DimensionMismatch("arrays could not be broadcast to a common size"))) _bcs1(a, b::Integer) = _bcs1(b, a) _bcs1(a, b) = _bcsm(b, a) ? b : (_bcsm(a, b) ? a : throw(DimensionMismatch("arrays could not be broadcast to a common size"))) # _bcsm tests whether the second index is consistent with the first _bcsm(a, b) = a == b || length(b) == 1 _bcsm(a, b::Number) = b == 1 _bcsm(a::Number, b::Number) = a == b || b == 1 ## Check that all arguments are broadcast compatible with shape # comparing one input against a shape check_broadcast_shape(shp) = nothing check_broadcast_shape(shp, ::Tuple{}) = nothing check_broadcast_shape(::Tuple{}, ::Tuple{}) = nothing check_broadcast_shape(::Tuple{}, Ashp::Tuple) = throw(DimensionMismatch("cannot broadcast array to have fewer dimensions")) function check_broadcast_shape(shp, Ashp::Tuple) _bcsm(shp[1], Ashp[1]) || throw(DimensionMismatch("array could not be broadcast to match destination")) check_broadcast_shape(tail(shp), tail(Ashp)) end check_broadcast_indices(shp, A) = check_broadcast_shape(shp, broadcast_indices(A)) # comparing many inputs @inline function check_broadcast_indices(shp, A, As...) check_broadcast_indices(shp, A) check_broadcast_indices(shp, As...) end ## Indexing manipulations # newindex(I, keep, Idefault) replaces a CartesianIndex `I` with something that # is appropriate for a particular broadcast array/scalar. `keep` is a # NTuple{N,Bool}, where keep[d] == true means that one should preserve # I[d]; if false, replace it with Idefault[d]. @inline newindex(I::CartesianIndex, keep, Idefault) = CartesianIndex(_newindex(I.I, keep, Idefault)) @inline _newindex(I, keep, Idefault) = (ifelse(keep[1], I[1], Idefault[1]), _newindex(tail(I), tail(keep), tail(Idefault))...) @inline _newindex(I, keep::Tuple{}, Idefault) = () # truncate if keep is shorter than I # newindexer(shape, A) generates `keep` and `Idefault` (for use by # `newindex` above) for a particular array `A`, given the # broadcast_indices `shape` # `keep` is equivalent to map(==, indices(A), shape) (but see #17126) @inline newindexer(shape, A) = shapeindexer(shape, broadcast_indices(A)) @inline shapeindexer(shape, indsA::Tuple{}) = (), () @inline function shapeindexer(shape, indsA::Tuple) ind1 = indsA[1] keep, Idefault = shapeindexer(tail(shape), tail(indsA)) (shape[1] == ind1, keep...), (first(ind1), Idefault...) end # Equivalent to map(x->newindexer(shape, x), As) (but see #17126) map_newindexer(shape, ::Tuple{}) = (), () @inline function map_newindexer(shape, As) A1 = As[1] keeps, Idefaults = map_newindexer(shape, tail(As)) keep, Idefault = newindexer(shape, A1) (keep, keeps...), (Idefault, Idefaults...) end @inline function map_newindexer(shape, A, Bs) keeps, Idefaults = map_newindexer(shape, Bs) keep, Idefault = newindexer(shape, A) (keep, keeps...), (Idefault, Idefaults...) end Base.@propagate_inbounds _broadcast_getindex(A, I) = _broadcast_getindex(containertype(A), A, I) # `(x,)`, where `x` is a scalar, broadcasts the same way as `[x]` or `x` Base.@propagate_inbounds _broadcast_getindex(::Type{Tuple}, A::Tuple{Any}, I) = A[1] Base.@propagate_inbounds _broadcast_getindex(::Type{Array}, A::Ref, I) = A[] Base.@propagate_inbounds _broadcast_getindex(::ScalarType, A, I) = A Base.@propagate_inbounds _broadcast_getindex(::Any, A, I) = A[I] ## Broadcasting core # nargs encodes the number of As arguments (which matches the number # of keeps). The first two type parameters are to ensure specialization. @generated function _broadcast!(f, B::AbstractArray, keeps::K, Idefaults::ID, A::AT, Bs::BT, ::Type{Val{N}}, iter) where {K,ID,AT,BT,N} nargs = N + 1 quote $(Expr(:meta, :inline)) # destructure the keeps and As tuples A_1 = A @nexprs $N i->(A_{i+1} = Bs[i]) @nexprs $nargs i->(keep_i = keeps[i]) @nexprs $nargs i->(Idefault_i = Idefaults[i]) @simd for I in iter # reverse-broadcast the indices @nexprs $nargs i->(I_i = newindex(I, keep_i, Idefault_i)) # extract array values @nexprs $nargs i->(@inbounds val_i = _broadcast_getindex(A_i, I_i)) # call the function and store the result result = @ncall $nargs f val @inbounds B[I] = result end end end # For BitArray outputs, we cache the result in a "small" Vector{Bool}, # and then copy in chunks into the output @generated function _broadcast!(f, B::BitArray, keeps::K, Idefaults::ID, A::AT, Bs::BT, ::Type{Val{N}}, iter) where {K,ID,AT,BT,N} nargs = N + 1 quote $(Expr(:meta, :inline)) # destructure the keeps and As tuples A_1 = A @nexprs $N i->(A_{i+1} = Bs[i]) @nexprs $nargs i->(keep_i = keeps[i]) @nexprs $nargs i->(Idefault_i = Idefaults[i]) C = Vector{Bool}(bitcache_size) Bc = B.chunks ind = 1 cind = 1 @simd for I in iter # reverse-broadcast the indices @nexprs $nargs i->(I_i = newindex(I, keep_i, Idefault_i)) # extract array values @nexprs $nargs i->(@inbounds val_i = _broadcast_getindex(A_i, I_i)) # call the function and store the result @inbounds C[ind] = @ncall $nargs f val ind += 1 if ind > bitcache_size dumpbitcache(Bc, cind, C) cind += bitcache_chunks ind = 1 end end if ind > 1 @inbounds C[ind:bitcache_size] = false dumpbitcache(Bc, cind, C) end end end """ broadcast!(f, dest, As...) Like [`broadcast`](@ref), but store the result of `broadcast(f, As...)` in the `dest` array. Note that `dest` is only used to store the result, and does not supply arguments to `f` unless it is also listed in the `As`, as in `broadcast!(f, A, A, B)` to perform `A[:] = broadcast(f, A, B)`. """ @inline broadcast!(f, C::AbstractArray, A, Bs::Vararg{Any,N}) where {N} = broadcast_c!(f, containertype(C), containertype(A, Bs...), C, A, Bs...) @inline function broadcast_c!(f, ::Type, ::Type, C, A, Bs::Vararg{Any,N}) where N shape = indices(C) @boundscheck check_broadcast_indices(shape, A, Bs...) keeps, Idefaults = map_newindexer(shape, A, Bs) iter = CartesianRange(shape) _broadcast!(f, C, keeps, Idefaults, A, Bs, Val{N}, iter) return C end # broadcast with computed element type @generated function _broadcast!(f, B::AbstractArray, keeps::K, Idefaults::ID, As::AT, ::Type{Val{nargs}}, iter, st, count) where {K,ID,AT,nargs} quote $(Expr(:meta, :noinline)) # destructure the keeps and As tuples @nexprs $nargs i->(A_i = As[i]) @nexprs $nargs i->(keep_i = keeps[i]) @nexprs $nargs i->(Idefault_i = Idefaults[i]) while !done(iter, st) I, st = next(iter, st) # reverse-broadcast the indices @nexprs $nargs i->(I_i = newindex(I, keep_i, Idefault_i)) # extract array values @nexprs $nargs i->(@inbounds val_i = _broadcast_getindex(A_i, I_i)) # call the function V = @ncall $nargs f val S = typeof(V) # store the result if S <: eltype(B) @inbounds B[I] = V else R = typejoin(eltype(B), S) new = similar(B, R) for II in Iterators.take(iter, count) new[II] = B[II] end new[I] = V return _broadcast!(f, new, keeps, Idefaults, As, Val{nargs}, iter, st, count+1) end count += 1 end return B end end # broadcast methods that dispatch on the type found by inference function broadcast_t(f, ::Type{Any}, shape, iter, As...) nargs = length(As) keeps, Idefaults = map_newindexer(shape, As) st = start(iter) I, st = next(iter, st) val = f([ _broadcast_getindex(As[i], newindex(I, keeps[i], Idefaults[i])) for i=1:nargs ]...) if val isa Bool B = similar(BitArray, shape) else B = similar(Array{typeof(val)}, shape) end B[I] = val return _broadcast!(f, B, keeps, Idefaults, As, Val{nargs}, iter, st, 1) end @inline function broadcast_t(f, T, shape, iter, A, Bs::Vararg{Any,N}) where N C = similar(Array{T}, shape) keeps, Idefaults = map_newindexer(shape, A, Bs) _broadcast!(f, C, keeps, Idefaults, A, Bs, Val{N}, iter) return C end # default to BitArray for broadcast operations producing Bool, to save 8x space # in the common case where this is used for logical array indexing; in # performance-critical cases where Array{Bool} is desired, one can always # use broadcast! instead. @inline function broadcast_t(f, ::Type{Bool}, shape, iter, A, Bs::Vararg{Any,N}) where N C = similar(BitArray, shape) keeps, Idefaults = map_newindexer(shape, A, Bs) _broadcast!(f, C, keeps, Idefaults, A, Bs, Val{N}, iter) return C end maptoTuple(f) = Tuple{} maptoTuple(f, a, b...) = Tuple{f(a), maptoTuple(f, b...).types...} # An element type satisfying for all A: # broadcast_getindex( # containertype(A), # A, broadcast_indices(A) # )::_broadcast_getindex_eltype(A) _broadcast_getindex_eltype(A) = _broadcast_getindex_eltype(containertype(A), A) _broadcast_getindex_eltype(::ScalarType, T::Type) = Type{T} _broadcast_getindex_eltype(::ScalarType, A) = typeof(A) _broadcast_getindex_eltype(::Any, A) = eltype(A) # Tuple, Array, etc. # An element type satisfying for all A: # unsafe_get(A)::unsafe_get_eltype(A) _unsafe_get_eltype(x::Nullable) = eltype(x) _unsafe_get_eltype(T::Type) = Type{T} _unsafe_get_eltype(x) = typeof(x) # Inferred eltype of result of broadcast(f, xs...) _broadcast_eltype(f, A, As...) = Base._return_type(f, maptoTuple(_broadcast_getindex_eltype, A, As...)) _nullable_eltype(f, A, As...) = Base._return_type(f, maptoTuple(_unsafe_get_eltype, A, As...)) # broadcast methods that dispatch on the type of the final container @inline function broadcast_c(f, ::Type{Array}, A, Bs...) T = _broadcast_eltype(f, A, Bs...) shape = broadcast_indices(A, Bs...) iter = CartesianRange(shape) if isleaftype(T) return broadcast_t(f, T, shape, iter, A, Bs...) end if isempty(iter) return similar(Array{T}, shape) end return broadcast_t(f, Any, shape, iter, A, Bs...) end @inline function broadcast_c(f, ::Type{Nullable}, a...) nonnull = all(hasvalue, a) S = _nullable_eltype(f, a...) if isleaftype(S) && null_safe_op(f, maptoTuple(_unsafe_get_eltype, a...).types...) Nullable{S}(f(map(unsafe_get, a)...), nonnull) else if nonnull Nullable(f(map(unsafe_get, a)...)) else Nullable{nullable_returntype(S)}() end end end @inline broadcast_c(f, ::Type{Any}, a...) = f(a...) @inline broadcast_c(f, ::Type{Tuple}, A, Bs...) = tuplebroadcast(f, tuplebroadcast_maxtuple(A, Bs...), A, Bs...) @inline tuplebroadcast(f, ::NTuple{N,Any}, As...) where {N} = ntuple(k -> f(tuplebroadcast_getargs(As, k)...), Val{N}) @inline tuplebroadcast(f, ::NTuple{N,Any}, ::Type{T}, As...) where {N,T} = ntuple(k -> f(T, tuplebroadcast_getargs(As, k)...), Val{N}) # When the result of broadcast is a tuple it can only come from mixing n-tuples # of the same length with scalars and 1-tuples. So, in order to have a # type-stable broadcast, we need to find a tuple of maximum length (except when # there are only scalars, empty tuples and 1-tuples, in which case the # returned value will be an empty tuple). # The following methods compare broadcast arguments pairwise to determine the # length of the final tuple. tuplebroadcast_maxtuple(A, B) = _tuplebroadcast_maxtuple(containertype(A), containertype(B), A, B) @inline tuplebroadcast_maxtuple(A, Bs...) = tuplebroadcast_maxtuple(A, tuplebroadcast_maxtuple(Bs...)) tuplebroadcast_maxtuple(A::NTuple{N,Any}, ::NTuple{N,Any}...) where {N} = A # Here we use the containertype trait to easier disambiguate between methods _tuplebroadcast_maxtuple(::Any, ::Any, A, B) = (nothing,) _tuplebroadcast_maxtuple(::Type{Tuple}, ::Any, A, B) = A _tuplebroadcast_maxtuple(::Any, ::Type{Tuple}, A, B) = B _tuplebroadcast_maxtuple(::Type{Tuple}, ::Type{Tuple}, A, B::Tuple{Any}) = A _tuplebroadcast_maxtuple(::Type{Tuple}, ::Type{Tuple}, A::Tuple{Any}, B) = B _tuplebroadcast_maxtuple(::Type{Tuple}, ::Type{Tuple}, A::Tuple{Any}, ::Tuple{Any}) = A _tuplebroadcast_maxtuple(::Type{Tuple}, ::Type{Tuple}, A, B) = throw(DimensionMismatch("tuples could not be broadcast to a common size")) tuplebroadcast_getargs(::Tuple{}, k) = () @inline tuplebroadcast_getargs(As, k) = (_broadcast_getindex(first(As), k), tuplebroadcast_getargs(tail(As), k)...) """ broadcast(f, As...) Broadcasts the arrays, tuples, `Ref`s, nullables, and/or scalars `As` to a container of the appropriate type and dimensions. In this context, anything that is not a subtype of `AbstractArray`, `Ref` (except for `Ptr`s), `Tuple`, or `Nullable` is considered a scalar. The resulting container is established by the following rules: - If all the arguments are scalars, it returns a scalar. - If the arguments are tuples and zero or more scalars, it returns a tuple. - If the arguments contain at least one array or `Ref`, it returns an array (expanding singleton dimensions), and treats `Ref`s as 0-dimensional arrays, and tuples as 1-dimensional arrays. The following additional rule applies to `Nullable` arguments: If there is at least one `Nullable`, and all the arguments are scalars or `Nullable`, it returns a `Nullable` treating `Nullable`s as "containers". A special syntax exists for broadcasting: `f.(args...)` is equivalent to `broadcast(f, args...)`, and nested `f.(g.(args...))` calls are fused into a single broadcast loop. ```jldoctest julia> A = [1, 2, 3, 4, 5] 5-element Array{Int64,1}: 1 2 3 4 5 julia> B = [1 2; 3 4; 5 6; 7 8; 9 10] 5×2 Array{Int64,2}: 1 2 3 4 5 6 7 8 9 10 julia> broadcast(+, A, B) 5×2 Array{Int64,2}: 2 3 5 6 8 9 11 12 14 15 julia> parse.(Int, ["1", "2"]) 2-element Array{Int64,1}: 1 2 julia> abs.((1, -2)) (1, 2) julia> broadcast(+, 1.0, (0, -2.0)) (1.0, -1.0) julia> broadcast(+, 1.0, (0, -2.0), Ref(1)) 2-element Array{Float64,1}: 2.0 0.0 julia> (+).([[0,2], [1,3]], Ref{Vector{Int}}([1,-1])) 2-element Array{Array{Int64,1},1}: [1, 1] [2, 2] julia> string.(("one","two","three","four"), ": ", 1:4) 4-element Array{String,1}: "one: 1" "two: 2" "three: 3" "four: 4" julia> Nullable("X") .* "Y" Nullable{String}("XY") julia> broadcast(/, 1.0, Nullable(2.0)) Nullable{Float64}(0.5) julia> (1 + im) ./ Nullable{Int}() Nullable{Complex{Float64}}() ``` """ @inline broadcast(f, A, Bs...) = broadcast_c(f, containertype(A, Bs...), A, Bs...) """ broadcast_getindex(A, inds...) Broadcasts the `inds` arrays to a common size like [`broadcast`](@ref) and returns an array of the results `A[ks...]`, where `ks` goes over the positions in the broadcast result `A`. ```jldoctest julia> A = [1, 2, 3, 4, 5] 5-element Array{Int64,1}: 1 2 3 4 5 julia> B = [1 2; 3 4; 5 6; 7 8; 9 10] 5×2 Array{Int64,2}: 1 2 3 4 5 6 7 8 9 10 julia> C = broadcast(+,A,B) 5×2 Array{Int64,2}: 2 3 5 6 8 9 11 12 14 15 julia> broadcast_getindex(C,[1,2,10]) 3-element Array{Int64,1}: 2 5 15 ``` """ broadcast_getindex(src::AbstractArray, I::AbstractArray...) = broadcast_getindex!(similar(Array{eltype(src)}, broadcast_indices(I...)), src, I...) @generated function broadcast_getindex!(dest::AbstractArray, src::AbstractArray, I::AbstractArray...) N = length(I) Isplat = Expr[:(I[$d]) for d = 1:N] quote @nexprs $N d->(I_d = I[d]) check_broadcast_indices(indices(dest), $(Isplat...)) # unnecessary if this function is never called directly checkbounds(src, $(Isplat...)) @nexprs $N d->(@nexprs $N k->(Ibcast_d_k = indices(I_k, d) == OneTo(1))) @nloops $N i dest d->(@nexprs $N k->(j_d_k = Ibcast_d_k ? 1 : i_d)) begin @nexprs $N k->(@inbounds J_k = @nref $N I_k d->j_d_k) @inbounds (@nref $N dest i) = (@nref $N src J) end dest end end """ broadcast_setindex!(A, X, inds...) Broadcasts the `X` and `inds` arrays to a common size and stores the value from each position in `X` at the indices in `A` given by the same positions in `inds`. """ @generated function broadcast_setindex!(A::AbstractArray, x, I::AbstractArray...) N = length(I) Isplat = Expr[:(I[$d]) for d = 1:N] quote @nexprs $N d->(I_d = I[d]) checkbounds(A, $(Isplat...)) shape = broadcast_indices($(Isplat...)) @nextract $N shape d->(length(shape) < d ? OneTo(1) : shape[d]) @nexprs $N d->(@nexprs $N k->(Ibcast_d_k = indices(I_k, d) == 1:1)) if !isa(x, AbstractArray) xA = convert(eltype(A), x) @nloops $N i d->shape_d d->(@nexprs $N k->(j_d_k = Ibcast_d_k ? 1 : i_d)) begin @nexprs $N k->(@inbounds J_k = @nref $N I_k d->j_d_k) @inbounds (@nref $N A J) = xA end else X = x @nexprs $N d->(shapelen_d = length(shape_d)) @ncall $N Base.setindex_shape_check X shapelen Xstate = start(X) @inbounds @nloops $N i d->shape_d d->(@nexprs $N k->(j_d_k = Ibcast_d_k ? 1 : i_d)) begin @nexprs $N k->(J_k = @nref $N I_k d->j_d_k) x_el, Xstate = next(X, Xstate) (@nref $N A J) = x_el end end A end end ############################################################ # x[...] .= f.(y...) ---> broadcast!(f, dotview(x, ...), y...). # The dotview function defaults to getindex, but we override it in # a few cases to get the expected in-place behavior without affecting # explicit calls to view. (All of this can go away if slices # are changed to generate views by default.) Base.@propagate_inbounds dotview(args...) = getindex(args...) Base.@propagate_inbounds dotview(A::AbstractArray, args...) = view(A, args...) Base.@propagate_inbounds dotview(A::AbstractArray{<:AbstractArray}, args::Integer...) = getindex(A, args...) ############################################################ # The parser turns @. into a call to the __dot__ macro, # which converts all function calls and assignments into # broadcasting "dot" calls/assignments: dottable(x) = false # avoid dotting spliced objects (e.g. view calls inserted by @view) dottable(x::Symbol) = !isoperator(x) || first(string(x)) != '.' || x == :.. # don't add dots to dot operators dottable(x::Expr) = x.head != :$ undot(x) = x function undot(x::Expr) if x.head == :.= Expr(:(=), x.args...) elseif x.head == :block # occurs in for x=..., y=... Expr(:block, map(undot, x.args)...) else x end end __dot__(x) = x function __dot__(x::Expr) dotargs = map(__dot__, x.args) if x.head == :call && dottable(x.args[1]) Expr(:., dotargs[1], Expr(:tuple, dotargs[2:end]...)) elseif x.head == :$ x.args[1] elseif x.head == :let # don't add dots to "let x=... assignments Expr(:let, dotargs[1], map(undot, dotargs[2:end])...) elseif x.head == :for # don't add dots to for x=... assignments Expr(:for, undot(dotargs[1]), dotargs[2]) elseif (x.head == :(=) || x.head == :function || x.head == :macro) && Meta.isexpr(x.args[1], :call) # function or macro definition Expr(x.head, x.args[1], dotargs[2]) else head = string(x.head) if last(head) == '=' && first(head) != '.' Expr(Symbol('.',head), dotargs...) else Expr(x.head, dotargs...) end end end """ @. expr Convert every function call or operator in `expr` into a "dot call" (e.g. convert `f(x)` to `f.(x)`), and convert every assignment in `expr` to a "dot assignment" (e.g. convert `+=` to `.+=`). If you want to *avoid* adding dots for selected function calls in `expr`, splice those function calls in with `\$`. For example, `@. sqrt(abs(\$sort(x)))` is equivalent to `sqrt.(abs.(sort(x)))` (no dot for `sort`). (`@.` is equivalent to a call to `@__dot__`.) ```jldoctest julia> x = 1.0:3.0; y = similar(x); julia> @. y = x + 3 * sin(x) 3-element Array{Float64,1}: 3.52441 4.72789 3.42336 ``` """ macro __dot__(x) esc(__dot__(x)) end end # module