# why doesn't \`@.\` always broadcast?

**URL:** https://discourse.julialang.org/t/why-doesnt-always-broadcast/130892
**Category:** New to Julia
**Tags:** broadcast
**Created:** [July 21, 2025, 3:34am UTC](https://discourse.julialang.org/t/why-doesnt-always-broadcast/130892 "2025-07-21T03:34:00Z")
**Posts on this page:** 13
**Page:** 1

<div class="post-metadata">

### Author: ![rokke](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rokke/32/222691_2.png) [@rokke](https://discourse.julialang.org/u/rokke)
#### Post date: [July 21, 2025, 3:34am UTC](https://discourse.julialang.org/t/why-doesnt-always-broadcast/130892/1 "2025-07-21T03:34:00Z")

</div>

some examples:

```julia
julia> @. x->x[1]
#1 (generic function with 1 method)

julia> ans( [[1,2,3],[9,8,7]] )
3-element Vector{Int64}:
 1
 2
 3

julia> @. x->getindex(x,1)
#3 (generic function with 1 method)

julia> ans( [[1,2,3],[9,8,7]] )
2-element Vector{Int64}:
 1
 9

julia> @. x->getindex.(x,1)
#5 (generic function with 1 method)

julia> ans( [[[1,2,3],[9,8,7]] ] )
1-element Vector{Vector{Int64}}:
 [1, 2, 3]

```

I was expecting #1 to behave like #3, and for #6 to return `[[1,9]]`. is there a way to make `@.` work all the time?

---

<div class="post-metadata">

### Author: ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)
#### Post date: [July 21, 2025, 5:05am UTC](https://discourse.julialang.org/t/why-doesnt-always-broadcast/130892/2 "2025-07-21T05:05:37Z")

</div>

`@.` doesn’t introduce broadcasting to everything that isn’t broadcasting. In this case, indexing syntax in the macro’s input expression does not count as a function call, even though it will later lower to a `getindex` call. The docstring for `@.` is pretty clear on what it’s supposed to do, and the `@macroexpand` result is very readable if you need to check:

```julia
julia> @macroexpand @. x->x[1] # doesn't touch indexing syntax
:(x->begin
          #= REPL[1]:1 =#
          x[1]
      end)

julia> @macroexpand @. x->getindex(x,1) # affects function call
:(x->begin
          #= REPL[3]:1 =#
          getindex.(x, 1)
      end)

julia> @macroexpand @. x->getindex.(x,1) # leaves broadcasted calls alone
:(x->begin
          #= REPL[4]:1 =#
          getindex.(x, 1)
      end)

help?> @.
  @. expr

  Convert every function call or operator in expr into a "dot call" (e.g. convert f(x) to f.(x)), and convert every
  assignment in expr to a "dot assignment" (e.g. convert += to .+=).

  If you want to avoid adding dots for selected function calls in expr, splice those function calls in with $. For
  example, @. sqrt(abs($sort(x))) is equivalent to sqrt.(abs.(sort(x))) (no dot for sort).

```

---

<div class="post-metadata">

### Author: ![rokke](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rokke/32/222691_2.png) [@rokke](https://discourse.julialang.org/u/rokke)
#### Post date: [July 21, 2025, 5:21am UTC](https://discourse.julialang.org/t/why-doesnt-always-broadcast/130892/3 "2025-07-21T05:21:33Z")

</div>

I was more fishing for an answer to the question at the end of the post; if maybe a more thorough broadcasting macro exists in a package somewhere, or a place to start for writing some `@..` which does distribute everything.

the `@macroexpand` call does help me though, thanks

---

<div class="post-metadata">

### Author: ![eldee](https://avatars.discourse-cdn.com/v4/letter/e/b5a626/32.png) [@eldee](https://discourse.julialang.org/u/eldee)
#### Post date: [July 21, 2025, 8:22am UTC](https://discourse.julialang.org/t/why-doesnt-always-broadcast/130892/4 "2025-07-21T08:22:24Z")

</div>

> [@rokke](#):
>
> I was more fishing for an answer to the question at the end of the post

> [@rokke](#):
>
> is there a way to make `@.` work all the time?

It’s not clear what exactly this question means. `@.` is working as intended. Conceptually it is just adding `.`s everywhere where they make sense. E.g. not in `x[1]`

```julia
julia> x = [[[1,2,3],[9,8,7]] ]; x.[1]
ERROR: syntax: invalid syntax "x.[1]" around REPL[...]:1

```

but it’s fine in `getindex`

```julia
julia> getindex.(x, 1)
1-element Vector{Vector{Int64}}:
 [1, 2, 3]

```

though not in `getindex.`

```julia
julia> getindex..(x, 1)
ERROR: UndefVarError: `..` not defined in `Main`

```

I’m sure there exist counter-examples to this intuitive idea, so check the source code or Benny`s reply to see what the macro is formally doing.

* * *

If you want your #6 to return `[[1, 9]]` you need something like

```julia
f1(x) = (a -> getindex.(a, 1)).(x)

```

i.e.

```julia
f2(x) = broadcast(a -> broadcast(b -> getindex(b, 1), a), x)

```

```julia-repl
julia> x = [[[1,2,3],[9,8,7]] ]; f1(x)
1-element Vector{Vector{Int64}}:
 [1, 9]

julia> 1-element Vector{Vector{Int64}}:
 [1, 9]

```

Is it possible to write a macro `@dotdot` so that `@dotdot getindex..(x, 1)` gets converted into one of these? Sure. But what for `x = [[[[1,2,3],[9,8,7]] ]]`? You’d need `getindex...(x, 1)`, which presumably will start conflicting with the splat operator `...`. If you want to keep `broadcasting` as deep as possible, note that you can broadcast over scalars (and e.g. `x = 5; getindex(x, 1)` works fine), so how do you know when to stop? In my opinion this would quickly become a convoluted mess which would be difficult to interpret.

* * *

I’m already not too fond of the `@.` macro itself, as to some extent it hides what’s really happening. E.g. that `@. x[1] + y` resolves to `x[1] .+ y` and not `x.[1] .+ y` or `getindex.(x, 1) .+ y`. It’s not a lot of effort to just manually add some dots yourself, and doing this manually forces you to check whether it makes sense. Also note that leaving out select broadcasts can [improve performance](https://docs.julialang.org/en/v1/manual/performance-tips/#man-performance-unfuse).

---

<div class="post-metadata">

### Author: ![rokke](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rokke/32/222691_2.png) [@rokke](https://discourse.julialang.org/u/rokke)
#### Post date: [July 21, 2025, 8:40am UTC](https://discourse.julialang.org/t/why-doesnt-always-broadcast/130892/5 "2025-07-21T08:40:35Z")

</div>

ah, sorry; I guess I wasn’t very clear as to what I was looking for. I understand that `@.` can’t be changed and that it’s working as described in its docs. what I wanted was a macro which broadcasts things exactly one time, and does so everywhere. so if say there was a `x[1]`, you would really be saying ‘the first element, of all elements of x’. or if there was a `getindex.(x, 1)`, it would be saying `getindex.(i, 1) for i in x` or however that syntax should look.

I agree that the `@.` macro isn’t very useful on its own, since the syntax it expands out to is not all that different from what you give it. but if there were a macro which meant broadcast _everything_ once, I would have plenty of use for it, since there are many cases where broadcasting is more than just adding a dot

maybe a for loop would be the `''`correct answer`''`, but I was hoping for a macro since the original source of this question was already part of a for loop:

```julia
for (a, b) in blackbox.(data) .|> (x->(getindex.(x,1), getindex.(x,2)))
...

```

would be much nicer as

```julia
for (a, b) in @.. blackbox(data) |> x->(x[1], x[2])
...

```

---

<div class="post-metadata">

### Author: ![eldee](https://avatars.discourse-cdn.com/v4/letter/e/b5a626/32.png) [@eldee](https://discourse.julialang.org/u/eldee)
#### Post date: [July 21, 2025, 9:36am UTC](https://discourse.julialang.org/t/why-doesnt-always-broadcast/130892/6 "2025-07-21T09:36:47Z")

</div>

If I understand correctly, the only problem with `@.` here is then that it does not support the bracket notation for indexing (and not that it doesn’t support double broadcasting (à la ‘`getindex..`’))? If so, that can be easily fixed:

```julia
using MacroTools: @capture, postwalk

function bracket_to_getindex(ex)
    postwalk(ex) do x
        @capture(x, A_[i__]) ? :(getindex($A, $(i...))) : x
    end
end

macro var".."(ex)
    return Broadcast. __dot__ (bracket_to_getindex(ex))
end

# Testing MWE
using StaticArrays

data = rand(SVector{2, SVector{3, SVector{4, Int}}})
blackbox(d) = d

for (a, b) in blackbox.(data) .|> (x->(getindex.(x,1), getindex.(x,2)))
    println("$a - $b")
end
# Int8[-82, 106, 68] - Int8[83, 62, 117]
# Int8[87, 78, -26] - Int8[-75, 119, -71]

for (a, b) in @.. blackbox(data) |> x->(x[1], x[2])
    println("$a - $b")
end
# Int8[-82, 106, 68] - Int8[83, 62, 117]
# Int8[87, 78, -26] - Int8[-75, 119, -71]

```

* * *

Edit: I do think an explicit for loop in the style of

```julia
for d in data
    bbd = blackbox(d)
    a = getindex.(bbd, 1)
    b = getindex.(bbd, 2)
    println("$a - $b")
end
# Int8[-82, 106, 68] - Int8[83, 62, 117]
# Int8[87, 78, -26] - Int8[-75, 119, -71]

```

is much easier to understand, though.

---

<div class="post-metadata">

### Author: ![rokke](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rokke/32/222691_2.png) [@rokke](https://discourse.julialang.org/u/rokke)
#### Post date: [July 21, 2025, 9:50am UTC](https://discourse.julialang.org/t/why-doesnt-always-broadcast/130892/7 "2025-07-21T09:50:24Z")

</div>

yes, that works beautifully for my current use case; will need to take a hot second to break down and digest exactly how it’s working though.

what I like about the double broadcasting aspect and such is the generality of it; the idea that the functions that you give it don’t matter because it preforms the same transformation over everything. if I was more well-versed in monads and such I’m sure I’d have a word for what I’m trying to get across

for my brain at least, the last for loop you include is a larger cognitive load since `data`, `a`, and `b` are all things which it makes sense to give a name to. creating an extra `d` and `bbd` which don’t have any logical interpretation other than ‘an entry of `data`’ or ‘a list of pairs of `a` and `b`’ means you have to keep bouncing around the logic to keep track of where everything is going

---

<div class="post-metadata">

### Author: ![eldee](https://avatars.discourse-cdn.com/v4/letter/e/b5a626/32.png) [@eldee](https://discourse.julialang.org/u/eldee)
#### Post date: [July 21, 2025, 11:36am UTC](https://discourse.julialang.org/t/why-doesnt-always-broadcast/130892/8 "2025-07-21T11:36:30Z")

</div>

> [@rokke](#):
>
> since `data`, `a`, and `b` are all things which it makes sense to give a name to. creating an extra `d` and `bbd` which don’t have any logical interpretation

Well, without the necessary context, none of the variables have any logical interpretation for me 😉  
But it might definitely be a fair point. I’m just giving my limited perspective here, based on my personal coding style and experience. Of course feel free to use a different style.

---

<div class="post-metadata">

### Author: ![rokke](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rokke/32/222691_2.png) [@rokke](https://discourse.julialang.org/u/rokke)
#### Post date: [July 21, 2025, 11:38am UTC](https://discourse.julialang.org/t/why-doesnt-always-broadcast/130892/9 "2025-07-21T11:38:40Z")

</div>

I was more giving an explanation as to why it looks nicer structured that way in my code; in another context bringing it out into a bigger loop might be nicer as well ^^

---

<div class="post-metadata">

### Author: ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)
#### Post date: [July 21, 2025, 11:45am UTC](https://discourse.julialang.org/t/why-doesnt-always-broadcast/130892/10 "2025-07-21T11:45:37Z")

</div>

If you want a reason why `A[i]` isn’t affected by `@.`, it’s because it’s really common to slice arrays for inputs in array operations `A[1:10] .+ B[3:13]`, maybe even lazily `@views A[1:10] .+ B[3:13]`. That’s why it has the bracket syntax instead of just being `getindex`/`view` calls in the first place. You could make a macro that affects bracket syntax as `getindex` calls, but why do that when you already have the option to write `getindex` calls? That macro would also have to interact with other macros like `@views` and adapt the `$` opt-out for bracket syntax somehow.

> [@eldee](#):
>
> I’m already not too fond of the `@.` macro itself, as to some extent it hides what’s really happening.

This is a common opinion. Manual dots was the more typical practice that Julia’s own dots emulated, and `@.` was always just a convenient macro for simpler array operations (which is exactly where bracket syntax is intended to remain as unbroadcasted `getindex`). For more complicated expressions, like those here involving anonymous functions, it’s bad form to use indiscriminate macros. `@.` was never intended as a go-to tool for adding a layer of broadcasting to everything in an arbitrary expression, nor was that something anybody wanted to do; things turned out the way it did for the preexisting context of array operations.

---

<div class="post-metadata">

### Author: ![stevengj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stevengj/32/71_2.png) [@stevengj](https://discourse.julialang.org/u/stevengj)
#### Post date: [July 21, 2025, 12:32pm UTC](https://discourse.julialang.org/t/why-doesnt-always-broadcast/130892/11 "2025-07-21T12:32:49Z")

</div>

> [@eldee](#):
>
> If I understand correctly, the only problem with `@.` here is then that it does not support the bracket notation for indexing

There’s no technical reason why this can’t be supported, and we’ve talked about it from the early days of dot-call broadcasting, but it never got implemented because we haven’t been able to decide on the exact semantics.

See several github issues: [Broadcast Array Indexing · Issue #19169 · JuliaLang/julia · GitHub](https://github.com/JuliaLang/julia/issues/19169), [make indexing expressions participate in dot syntax fusion · Issue #22858 · JuliaLang/julia · GitHub](https://github.com/JuliaLang/julia/issues/22858), [Broadcasting pointwise indexing operator · Issue #2591 · JuliaLang/julia · GitHub](https://github.com/JuliaLang/julia/issues/2591)

---

<div class="post-metadata">

### Author: ![AntonReinhard](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/antonreinhard/32/211306_2.png) [@AntonReinhard](https://discourse.julialang.org/u/AntonReinhard)
#### Post date: [July 21, 2025, 1:18pm UTC](https://discourse.julialang.org/t/why-doesnt-always-broadcast/130892/12 "2025-07-21T13:18:52Z")

</div>

> [@Benny](#):
>
> The docstring for `@.` is pretty clear on what it’s supposed to do

Just to add my two cents, from my perspective it’s not that clear. You quote the docs as:

```julia
Convert every function call or operator in expr into a "dot call"

```

At least in my experience, `[]` notation is usually referred to as the index operator, so I would have assumed this to be included here. Maybe the docs could add a note for these exceptions.  
Another exception is the `.` operator, as in the `getfield` operator.

I’m probably going a bit off-topic here, but I do sometimes wish there was a `..` operator to broadcast a `getfield` call, since using `getfield.` usually results in type instability:

```julia
julia> struct A x::Int; y::Float64 end

julia> v = [A(1, 1.0), A(2, 2.0), A(3, 3.0)]
3-element Vector{A}:
 A(1, 1.0)
 A(2, 2.0)
 A(3, 3.0)

julia> @code_warntype getfield.(v, :x)
MethodInstance for (::var"##dotfunction#230#1")(::Vector{A}, ::Symbol)
  from (::var"##dotfunction#230#1")(x1, x2) @ Main none:0
Arguments
  #self#::Core.Const(var"##dotfunction#230#1"())
  x1::Vector{A}
  x2::Symbol
Body::Union{Vector{Float64}, Vector{Int64}, Vector{Real}}
1 ─ %1 = Base.broadcasted(Main.getfield, x1, x2)::Base.Broadcast.Broadcasted{Base.Broadcast.DefaultArrayStyle{1}, Nothing, typeof(getfield), Tuple{Vector{A}, Base.RefValue{Symbol}}}
│ %2 = Base.materialize(%1)::Union{Vector{Float64}, Vector{Int64}, Vector{Real}}
└── return %2

```

Unless you wrap it in an extra function, but that is kinda tedious:

```julia
julia> f(x::A) = x.x # works the same with getfield(x, :x) too
f (generic function with 1 method)

julia> @code_warntype f.(v)
MethodInstance for (::var"##dotfunction#231#2")(::Vector{A})
  from (::var"##dotfunction#231#2")(x1) @ Main none:0
Arguments
  #self#::Core.Const(var"##dotfunction#231#2"())
  x1::Vector{A}
Body::Vector{Int64}
1 ─ %1 = Base.broadcasted(Main.f, x1)::Base.Broadcast.Broadcasted{Base.Broadcast.DefaultArrayStyle{1}, Nothing, typeof(f), Tuple{Vector{A}}}
│ %2 = Base.materialize(%1)::Vector{Int64}
└── return %2

```

It seems like for some reason, the normal `getfield` can constant propagate the Symbol, but the `getfield.` call can’t.

---

<div class="post-metadata">

### Author: ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)
#### Post date: [July 21, 2025, 3:00pm UTC](https://discourse.julialang.org/t/why-doesnt-always-broadcast/130892/13 "2025-07-21T15:00:06Z")

</div>

> [@AntonReinhard](#):
>
> At least in my experience, `[]` notation is usually referred to as the index operator, so I would have assumed this to be included here. Maybe the docs could add a note for these exceptions.  
> Another exception is the `.` operator, as in the `getfield` operator.

Definitely opening up another can of worms here, but whether those count as operators or just syntax/notation elements is much more inconsistent than for `+,-,*,/`. C/C++ puts them among member access operators, but Python/MATLAB/Julia doesn’t. Julia does document lists of operators, but for specific purposes rather than an exhaustive list, and neither `.` for `getproperty` (slight correction there, though it usually inlines `getfield` anyway) nor `[]` brackets are described on that page. Despite deliberately using “dot operator” for dotted versions of binary operators however, the symbol `:.` is in fact on the “operator precedence” list and accepted by `Base.operator_precedence`. On the other hand, there is no possible way to make a symbol for brackets and its precedence is as obvious as `()` parentheses, which also aren’t on that list.

> [@AntonReinhard](#):
>
> the normal `getfield` can constant propagate the Symbol, but the `getfield.` call can’t.

That’s because `getfield.` isn’t a derivative function to be called. `getfield` is just an input for a higher order function in a series of calls lowered from the dot syntax. By keeping the symbol `:x` as a separate _input_ to those calls instead of a constant in a nested `Core.getfield` call like `(a -> a.x).(v)`, we don’t have constant propagation privileges anymore; the `:x` is currently stored in a `Base.RefValue{Symbol}(:x)` element, which is how the compiler ends up only seeing `Symbol`.

> [@AntonReinhard](#):
>
> `@code_warntype getfield.(v, :x)`

`@code_warntype` won’t show constant propagation even if it would occur for nested calls, this is what happens for dots:

```julia
julia> @code_warntype A(1,2.3).re
MethodInstance for getproperty(::A, ::Symbol)
...
Body::Union{Float64, Int64}
...

```

> [@AntonReinhard](#):
>
> I do sometimes wish there was a `..` operator to broadcast a `getfield` call

I doubt any improvement to constant propagation would need a separate `..` notation, and it’s very far removed from anything suggested for it so far.
