Using @macroexpand I realised the error seems to be in the difference of rand.(10) and rand(10) as
(+).(rand(10), 2) # works
(+).(rand.(10), 2) # does not work, MethodError: no method matching +(::Vector{Float64}, ::Int64)
which seems weird to me, as rand(10) produces a Vector of floats, but so does rand.(10), hence there seems to be a method available to add a float vector and an integer.
What am I missing here?
Thank you very much in advance!
Yeah, this is a very confusing one, verging on cursed. One way to think of it is that broadcast is creating a simple loop over all the dotted expressions, so you can think of rand.(xs) .+ ys as something kinda like [rand(x) + y for x in xs, y in ys]. But that won’t work because rand(10) gives you a vector and you can’t add (without broadcasting, but remember we already did the broadcast transform) a scalar to a vector.
There are a few things that make this especially confusing:
5 .* rand.(10) works, because you can multiply a scalar by a vector.
rand.(10) — by itself — is functionally equivalent to rand(10) because, at the end of a broadcast expression, Julia “unwraps” zero-dimensional results. Theoretically, the “correct” answer to rand.(10) would be a zero-dimensional array that contains a 10-element vector, but that’s quite cumbersome in many situations. See julia#28866 for more on this.
The simplest guideline is that broadcasting works most seamlessly for functions functions that take scalars and return a single value. To avoid @. from affecting functions like rand (or ones or zeros or any of the like), you can “protect” them with a $. That is, @. 5 * $rand(10) + 2.
Note also that if you have something else to provide the shape of the array, then you can just use rand.().
For example, with a pre-allocated output x = zeros(10), you can do:
@. x = 5 * rand() + 2
and it will expand out to a separate call to rand() for each element, without ever allocating an array of random numbers. Another example would be where you are combining random number with some other array, e.g.
Using another example, I don’t understand why this fails
5 .* zeros.() .+ 2
# MethodError: no method matching +(::Array{Float64, 0}, ::Int64)
# For element-wise addition, use broadcasting with dot syntax: array .+ scalar
but this works
x = 5 .* zeros.()
x .+ 2
# 2.0
I guess x provided the shape for the broadcasting as in @stevengj answer, but what is the intention of the behaviour of the first example? Even the error message is basically saying I did it correct
Think of assigning this to an array — the broadcast means that each element will be computed as 5 * zeros() + 2, but this fails because zeros() returns a 0-dimensional array, and 5 * zeros() (scalar * array = array) is a 0-dimensional array, but Julia doesn’t define a method for array + scalar for any dimensionality of array: