Very interesting readings.
So, let’s see if I got it right this time.
From using Meta.@lower
I see that ones.(2,2).*rand.(2,2)
is basically being “translated” into
julia> using .Broadcast: materialize, broadcasted
julia> bc = broadcasted(*, broadcasted(ones, 2, 2), broadcasted(rand, 2, 2))
Base.Broadcast.Broadcasted(*, (Base.Broadcast.Broadcasted(ones, (2, 2)), Base.Broadcast.Broadcasted(rand, (2, 2))))
julia> materialize(bc)
2×2 Array{Float64,2}:
1.08929 0.944512
1.08929 0.944512
here bc
holds everything necessary to “unfold” the computation, which if I understand correctly is then only performed by the materialize
call.
Somewhat differently instead, if I call ones(2,2).*rand(2,2)
this translates into
julia> bc = broadcasted(*, ones(2,2), rand(2,2))
Base.Broadcast.Broadcasted(*, ([1.0 1.0; 1.0 1.0], [0.19239965952523885 0.8796361629139042; 0.1939935696169941 0.0831963706586214]))
julia> materialize(bc)
2×2 Array{Float64,2}:
0.1924 0.879636
0.193994 0.0831964
which I could also achieve by writing
a, b = broadcasted(ones, 2, 2), broadcasted(rand, 2, 2)
bc = broadcasted(*, materialize(a), materialize(b))
materialize(bc)
So, I went and checked how materialize
works. I’m not entirely sure I understand it but, from what I got, it instantiate
s the (otherwise lazy) Broadcasted
object.
So the difference between calling it only only on the “fused” expression and calling it in each single case is:
- doing
@. ones(2,2)*rand(2,2)
does not “apply” (instantiate
) any function/object until the very end
- doing
broadcasted(*, ones(2,2), rand(2,2))
instead “instantiate
s” the two arrays before calling the broadcast operation (though instantiate
is probably not the correct term here).
I think I got it. I still find it unintuitive from a “high-level” perspective, but at least I understand a bit what’s going on now (which will hopefully prevent bugs in the future).