Consider the following accumulator type, which works like an array in that you can push things to it, but only tracks its mean:
mutable struct Accumulator{T}
data::T
count::Int64
end
function Base.push!(acc::Accumulator, term)
acc.data += term # <-- in-place addition
acc.count += 1
acc
end
mean(acc::Accumulator) = acc.data ./ acc.count
I want this to work for T
being a scalar or an array type. However,
it turns out that for T
being an array type, the addition in push!
creates a temporary. This is because in Julia, x+=a
is equivalent to x=x+a
, and I suspect Julia cannot guarantee that acc.data
and term
do not alias.
A simple fix is to replace +=
with element-wise addition, .+=
. However, this will then break scalar types, which do not allow this. So the only way I came up with to fix this problem is to add a specialization of the following form:
function Base.push!(acc::Accumulator, term::AbstractArray)
acc.data .+= term # <-- element-wise addition
acc.count += 1
acc
end
This is however somewhat ugly and also brittle... does anyone know a better way of doing this, preferrably in a generic fashion and without the temporary creation?
Oddly enough,
Number
s are iterable in Julia, but that doesn't seem to help us here, because there is nosetindex!
method forNumber
s.Here are two different approaches. The first uses iterator traits and the second just patches up the method signatures a bit to address corner cases.
Iterator traits
We can use the
IteratorSize
trait to distinguish between scalars and vectors. For scalars,Base.IteratorSize(x)
returnsBase.HasShape{0}
. For arrays,Base.IteratorSize(x)
returnsBase.HasShape{N}
, whereN
is the number of dimensions of the array.In action at the REPL:
Patching the method signatures
Instead of using iterator traits, we could just make a couple small tweaks to your
push!
method signatures to prevent pushing an array onto a scalar.Now we get a sensible error message if we try to push an array onto a scalar: