Generally, yes - writing geman_mcclure.(d2, s)
with d2
a Vector
and s
a scalar (or even a vector of the same length as d2
) is equivalent to
out_arr = similar(d2)
for i in eachindex(d2)
out_arr[i] = geman_mcclure(d2[i], s) # geman_mcclure(d2[i], s[i]) if `s` is broadcastable
end
out_arr
which is the same as your explicitly vectorized geman_mcclure!
function, modulo the additional allocation, which can be done ahead of time manually. If done ahead of time, you can (just with geman_mcclure
) then write w .= geman_mcclure.(d2, s)
, which is already equivalent to the loop above, except that out_arr
does not have to be allocated, because it already exists.
This is a common pattern - instead of writing explicitly vectorized versions of scalar functions like geman_mcclure!
, we usually only have the scalar version and broadcast/inplace-broadcast/map as necessary, allowing loop fusion to take place.
In comparison, geman_mcclure!
does not allow loop fusion with broadcast outside of itself. That is, this example will not fuse loops:
z .= a .+ geman_mcclure!(w, ds2, s)
(there will first be the inner loop inside of geman_mcclure!
, then the outer one over z
and a
) whereas this will:
z .= a .+ geman_mcclure.(ds2, s)
which is in fact equivalent to
for idx in eachindex(z, a, ds2)
z[idx] = a[idx] + geman_mcclure(ds2[idx], s)
end
This is also what I mean with “broadcasting inline” - simply using the scalar version in a broadcast is more flexible than having geman_mcclure!
, which is required to complete before the outer broadcast can be calculated. The general rule of thumb for when I use explicitly modifying functions is either when the operation is not a scalar operation (i.e., more than a single index is required to perform the calculation, forcing you to write a loop either way) or when there’s a truly persistent modification of a datastructure as a sideeffect happens, e.g. with get!
:
help?> get!
search: get! mergewith! get getpid getkey getfield getindex getglobal
get!(collection, key, default)
Return the value stored for the given key, or if no mapping for the
key is present, store key => default, and return default.
Examples
≡≡≡≡≡≡≡≡≡≡
julia> d = Dict("a"=>1, "b"=>2, "c"=>3);
julia> get!(d, "a", 5)
1
julia> get!(d, "d", 4)
4
julia> d
Dict{String, Int64} with 4 entries:
"c" => 3
"b" => 2
"a" => 1
"d" => 4