# Understanding the behaviour of @. macro

Hi,

I am very new to Julia and was playing around with `@.` (using Julia 1.10.3) and noticed that I can compute the expression

``````5 .* rand(10) .+ 2
``````

as expected, however when I use

``````@. 5*rand(10) + 2
``````

it does not work and gives the error “MethodError: no method matching +(::Vector{Float64}, ::Int64)”.

``````@macroexpand @. 5*rand(10) + 2
:((+).((*).(5, rand.(10)), 2))
``````

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!

3 Likes

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`.

16 Likes

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.

``````@. 5 * rand() + (1:10)
``````
5 Likes

into the blog it goes

1 Like

Thank you very much for the insightful answer!

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:

``````julia> zeros()
0-dimensional Array{Float64, 0}:
0.0

julia> zeros() + 1
ERROR: MethodError: no method matching +(::Array{Float64, 0}, ::Int64)
``````

In contrast, it works with `rand.()` because `rand()` returns a scalar.

1 Like