# Should scalar calculation in Broadcast be "lazy"?

**URL:** <https://discourse.julialang.org/t/should-scalar-calculation-in-broadcast-be-lazy/44664>\
**Category:** Internals & Design\
**Tags:** broadcast\
**Created:** [August 10, 2020, 7:55am UTC](https://discourse.julialang.org/t/should-scalar-calculation-in-broadcast-be-lazy/44664 "2020-08-10T07:55:45Z")\
**Posts on this page:** 13\
**Page:** 1

<div class="post-metadata">

**Author:** ![N5N3](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/n5n3/32/17663_2.png) [@N5N3](https://discourse.julialang.org/u/N5N3)\
**Post date:** [August 10, 2020, 7:55am UTC](https://discourse.julialang.org/t/should-scalar-calculation-in-broadcast-be-lazy/44664/1 "2020-08-10T07:55:45Z")

</div>

At present, the broadcast in julia is always lazy, no matter the inputs’ axes.

I think, at least for numeric calcution, if both the input(s) and output are scalar, we could perform the calculation immediately, as the storage of Intermediate variable is always **negligible**. In other words we might merge the scalars during the `broadcasted` chain.

At present, a pre-defined function could merge const scalars well, like:

```julia
julia> a = randn(1000); b = randn(1000); c = similar(a,(1000,1000));

julia> f(x,y) = sin(exp(2pi)*x*y);

julia> @btime @. $c = f($a,$b');
  16.042 ms (0 allocations: 0 bytes)

```

while, a nested Broadcasted object not:

```julia
julia> @btime @. $c = sin(exp(2pi)*$a*$b');
  38.451 ms (0 allocations: 0 bytes)

```

Of course, we could avoid it by merge the scalars ourselves, like:

```julia
julia> temp = exp(2pi); @btime @. $c = sin($temp*$a*$b');
  16.034 ms (0 allocations: 0 bytes)

```

But I think such operation could be done during the `broadcasted` chain, just add a muti-dispatch like:

```julia
const AbstractScalar = Union{Number,AbstractArray{<:Number,0}}
broadcasted(::S, f, args::Vararg{AbstractScalar}) where {S<:BroadcastStyle}=
    combine_eltypes(f, args) <: Number ? f(map(first,args)...) : Broadcasted{S}(f, args)

```

and

```julia
@btime @. $c = sin(exp(2pi)*$a*$b');
  16.306 ms (0 allocations: 0 bytes)

```

Since `a * b * c` will be transformed to `*(a,b,c)`, the `broadcasteded` call for these expandable funtion should be expanded at once, i.e.

```julia
for op in (:+, :*, :&, :|, :xor, :min, :max, :kron)
    @eval begin
        @inline broadcasted(::typeof($op),x,y) = begin
            x′ = broadcastable(x)
            y′ = broadcastable(y)
            broadcasted(combine_styles(x′,y′), $op, x′, y′)
        end
        broadcasted(::typeof($op),x,y,args...) = begin
            temp = broadcasted($op,x,y)
            broadcasted($op,temp,args...)
        end
    end
end

```

---

<div class="post-metadata">

**Author:** ![Per](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/per/32/10387_2.png) [@Per](https://discourse.julialang.org/u/Per)\
**Post date:** [August 10, 2020, 11:58am UTC](https://discourse.julialang.org/t/should-scalar-calculation-in-broadcast-be-lazy/44664/2 "2020-08-10T11:58:15Z")

</div>

Maybe one shouldn’t change how the dot notation works, but only modify the `@.` macro to map to a version of broadcast that is not lazy for scalars.

When writing out each dot explicitly, it is easy to control exactly which operations are lazy. e.g.

```julia
@btime $c .= sin.(exp(2pi).*$a.*$b') # (fast) 

```

Sometimes broadcasted operations have side effects, for example:

```julia
println.(v) # print each element of v on a separate line

```

Hence, changing what operations are lazy will be breaking.

---

<div class="post-metadata">

**Author:** ![N5N3](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/n5n3/32/17663_2.png) [@N5N3](https://discourse.julialang.org/u/N5N3)\
**Post date:** [August 10, 2020, 12:17pm UTC](https://discourse.julialang.org/t/should-scalar-calculation-in-broadcast-be-lazy/44664/3 "2020-08-10T12:17:44Z")

</div>

I agree with compatibility is important, so a constraint that all the inputs and output are **scalar number** s is made.

Scalar inputs ensure that the function will be only called once, and scalar output ensure that it can be savely replaced with a non-lazy version.

I didn’t run a test for this, if there’s other side effect, may be we can add some constaints for the broadcasted function, just like the broadcast between **Range** and **Scalar** / **Range** and **Range**

After all, the expansion of `+(a,b,c)` is need for consistency.  
At present:

```julia
julia> @. (1:2) + (2:3) + 1
2-element Array{Int64,1}:
 4
 6

```

while

```julia
julia> (1:2) .+ (2:3) .+ 1
4:2:6

```

---

<div class="post-metadata">

**Author:** ![Per](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/per/32/10387_2.png) [@Per](https://discourse.julialang.org/u/Per)\
**Post date:** [August 10, 2020, 12:30pm UTC](https://discourse.julialang.org/t/should-scalar-calculation-in-broadcast-be-lazy/44664/4 "2020-08-10T12:30:56Z")

</div>

A scalar function might be called more than once. For example, if I want to print out a string three times, I can do this:

```julia
@. isnothing(println("Hello")) * (1:3);

```

Changing how broadcasting works might make this code run faster, but then it would only print the string once, which would be a breaking change.

---

<div class="post-metadata">

**Author:** ![N5N3](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/n5n3/32/17663_2.png) [@N5N3](https://discourse.julialang.org/u/N5N3)\
**Post date:** [August 10, 2020, 12:48pm UTC](https://discourse.julialang.org/t/should-scalar-calculation-in-broadcast-be-lazy/44664/5 "2020-08-10T12:48:29Z")

</div>

I tried it, and it accually print 3 times with the additional dispatch. As

```julia
Meta.lower(Main,:(@. isnothing(println("Hello")) * (1:3);))
:($(Expr(:thunk, CodeInfo(
    @ none within `top-level scope'
1 ─ %1 = Base.broadcasted(println, "Hello")
│ %2 = Base.broadcasted(isnothing, %1)
│ %3 = 1:3
│ %4 = Base.broadcasted(*, %2, %3)
│ %5 = Base.materialize(%4)
└── return %5
))))

```

The first line return nothing, thus it keeps lazy.

But if we define a function like `f(x) = isnothing(println("Hello"))`, and call `@. f(1) * (1:3)`, only one line will be printed.

As Scalar could also be seen as special `Range`, maybe we should limit this within built-in math function.

---

<div class="post-metadata">

**Author:** ![Per](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/per/32/10387_2.png) [@Per](https://discourse.julialang.org/u/Per)\
**Post date:** [August 10, 2020, 12:58pm UTC](https://discourse.julialang.org/t/should-scalar-calculation-in-broadcast-be-lazy/44664/6 "2020-08-10T12:58:20Z")

</div>

> [@N5N3](#):
>
> But if we define a function like `f(x) = isnothing(println("Hello"))` , and call `@. f(1) * (1:3)` , only one line will be printed.

Really? That would be a bug then. On which version of Julia did you get this result?

---

<div class="post-metadata">

**Author:** ![N5N3](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/n5n3/32/17663_2.png) [@N5N3](https://discourse.julialang.org/u/N5N3)\
**Post date:** [August 10, 2020, 1:05pm UTC](https://discourse.julialang.org/t/should-scalar-calculation-in-broadcast-be-lazy/44664/7 "2020-08-10T13:05:11Z")

</div>

Not a bug, the result of one “Hello” happened only if the addition dispatch has been created. 😀

---

<div class="post-metadata">

**Author:** ![Sukera](https://avatars.discourse-cdn.com/v4/letter/s/ce7236/32.png) [@Sukera](https://discourse.julialang.org/u/Sukera)\
**Post date:** [August 10, 2020, 1:09pm UTC](https://discourse.julialang.org/t/should-scalar-calculation-in-broadcast-be-lazy/44664/8 "2020-08-10T13:09:58Z")

</div>

```julia
julia> @. rand()*[1 1 1]
1×3 Array{Float64,2}:
 0.868946 0.0510332 0.869655

```

`rand()` produces a scalar number, yet modifying the behaviour changes the outcome and would thus be breaking.

You can achieve the behaviour you want by wrapping your expression in `Ref`:

```julia
julia> @. Ref(rand())[]*[1 1 1]
1×3 Array{Float64,2}:
 0.12594 0.12594 0.12594

```

---

<div class="post-metadata">

**Author:** ![Per](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/per/32/10387_2.png) [@Per](https://discourse.julialang.org/u/Per)\
**Post date:** [August 10, 2020, 1:10pm UTC](https://discourse.julialang.org/t/should-scalar-calculation-in-broadcast-be-lazy/44664/9 "2020-08-10T13:10:53Z")

</div>

> [@N5N3](#):
>
> Not a bug, the result of one “Hello” happened only if the addition dispatch has been created. 😀

Not sure what you mean. I tried this on Julia 1.0.5, Julia 1.4.2 and 1.5.0. In each case it prints “Hello” three times.  
(In Julia 1.0.5 the function needs to be written as `println("Hello") isa Nothing`, but the result is the same.)

---

<div class="post-metadata">

**Author:** ![N5N3](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/n5n3/32/17663_2.png) [@N5N3](https://discourse.julialang.org/u/N5N3)\
**Post date:** [August 10, 2020, 1:20pm UTC](https://discourse.julialang.org/t/should-scalar-calculation-in-broadcast-be-lazy/44664/10 "2020-08-10T13:20:54Z")

</div>

I meant I ran the code below before test.

```julia
const AbstractScalar = Union{Number,AbstractArray{<:Number,0}}
broadcasted(::S, f, args::Vararg{AbstractScalar}) where {S<:BroadcastStyle}=
    combine_eltypes(f, args) <: Number ? f(map(first,args)...) : Broadcasted{S}(f, args)

```

Anyway, Sukera has shown another example with `rand()`.

And the “non-lazy” is far from compatible.

---

<div class="post-metadata">

**Author:** ![Per](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/per/32/10387_2.png) [@Per](https://discourse.julialang.org/u/Per)\
**Post date:** [August 10, 2020, 1:29pm UTC](https://discourse.julialang.org/t/should-scalar-calculation-in-broadcast-be-lazy/44664/11 "2020-08-10T13:29:38Z")

</div>

I see. Yes, the `rand()` example is much better (and much more likely to appear in real code).

Fundamentally, this is a question of “do what I mean” vs “do what I say”. Julia typically stands on the “what I say” side, while many older languages contain a few “what I mean” heuristics. I like the fact that adding a dot guarantees that a function call will be broadcasted. If I don’t want that, I’ll just omit the dot…

---

<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:** [August 10, 2020, 2:51pm UTC](https://discourse.julialang.org/t/should-scalar-calculation-in-broadcast-be-lazy/44664/12 "2020-08-10T14:51:38Z")

</div>

> [@Sukera](#):
>
> You can achieve the behaviour you want by wrapping your expression in `Ref` :

Much easier to just use `$` to suppress broadcasting of specific functions with `@.`:

```julia
julia> @. $rand() * [1,1,1]
3-element Array{Float64,1}:
 0.45567040553963833
 0.45567040553963833
 0.45567040553963833

```

This is described in the [documentation for `@.`](https://docs.julialang.org/en/latest/base/arrays/#Base.Broadcast.@ __dot__ ).

> [@Per](#):
>
> Maybe one shouldn’t change how the dot notation works, but only modify the `@.` macro to map to a version of broadcast that is not lazy for scalars.

Besides being breaking, I don’t think this is possible. Like any macro, `@.` is a _syntactic_ transformation. It only knows how the expression “spelled”, not what anything _means_ — it doesn’t know which functions or variables represent “scalars” and which are vectors.

---

<div class="post-metadata">

**Author:** ![Sukera](https://avatars.discourse-cdn.com/v4/letter/s/ce7236/32.png) [@Sukera](https://discourse.julialang.org/u/Sukera)\
**Post date:** [August 10, 2020, 3:22pm UTC](https://discourse.julialang.org/t/should-scalar-calculation-in-broadcast-be-lazy/44664/13 "2020-08-10T15:22:14Z")

</div>

Oh wow, I did not know about that! I might begin to use `@.` then 😃
