# Allow 3-argument sum in Julia?

**URL:** <https://discourse.julialang.org/t/allow-3-argument-sum-in-julia/80457>\
**Category:** General Usage\
**Created:** [May 4, 2022, 2:17am UTC](https://discourse.julialang.org/t/allow-3-argument-sum-in-julia/80457 "2022-05-04T02:17:04Z")\
**Posts on this page:** 17\
**Page:** 1

<div class="post-metadata">

**Author:** ![Seif\_Shebl](https://avatars.discourse-cdn.com/v4/letter/s/eada6e/32.png) [@Seif\_Shebl](https://discourse.julialang.org/u/Seif_Shebl)\
**Post date:** [May 4, 2022, 2:17am UTC](https://discourse.julialang.org/t/allow-3-argument-sum-in-julia/80457/1 "2022-05-04T02:17:04Z")

</div>

Julia’s `sum` accepts a function as a first argument, like `sum(abs, arr)`. This feature becomes very useful and efficient in many situations when we want to apply the function on the array before sum.  
However, if the function has 2 arguments, `sum` doesn’t work because now it has 3 arguments. For example,

```julia
a = rand(Bool, 24)
b = rand(Bool, 24)
sum(xor, a, b)

```

fails with `ERROR: MethodError: no method matching sum(::typeof(xor), ::Vector{Int64}, ::Vector{Int64})`. In this case, we have to do something like:

```julia
sum(a[i] ⊻ b[i] for i in eachindex(a,b))
# or
mapreduce(t->xor(t...), +, zip(a,b))

```

How about allowing `sum(xor, a, b)` to mean `sum(a[i] ⊻ b[i] for i in eachindex(a,b))`? This seems very elegant and clean while efficient as well.

---

<div class="post-metadata">

**Author:** ![aramirezreyes](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aramirezreyes/32/42573_2.png) [@aramirezreyes](https://discourse.julialang.org/u/aramirezreyes)\
**Post date:** [May 4, 2022, 2:44am UTC](https://discourse.julialang.org/t/allow-3-argument-sum-in-julia/80457/2 "2022-05-04T02:44:11Z")

</div>

I acknowledge I am not addressing the question about `sum`, but why not:

```julia
julia> a = rand(Bool, 24)
julia> b = rand(Bool, 24)
julia> mapreduce(xor,+,a,b)
12

```

?

---

<div class="post-metadata">

**Author:** ![Seif\_Shebl](https://avatars.discourse-cdn.com/v4/letter/s/eada6e/32.png) [@Seif\_Shebl](https://discourse.julialang.org/u/Seif_Shebl)\
**Post date:** [May 4, 2022, 3:13am UTC](https://discourse.julialang.org/t/allow-3-argument-sum-in-julia/80457/3 "2022-05-04T03:13:29Z")

</div>

Yes, this is better but still more verbose than just `sum(xor, a, b)`. I knew about this alternative but didn’t scan all possible variants in my post. It is still a good feature to allow it for `sum`, IMO.

---

<div class="post-metadata">

**Author:** ![photor](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/photor/32/14343_2.png) [@photor](https://discourse.julialang.org/u/photor)\
**Post date:** [May 4, 2022, 3:18am UTC](https://discourse.julialang.org/t/allow-3-argument-sum-in-julia/80457/4 "2022-05-04T03:18:57Z")

</div>

Just define

```julia
sum(f, a, b)=mapreduce(f, +, a, b)

```

---

<div class="post-metadata">

**Author:** ![Seif\_Shebl](https://avatars.discourse-cdn.com/v4/letter/s/eada6e/32.png) [@Seif\_Shebl](https://discourse.julialang.org/u/Seif_Shebl)\
**Post date:** [May 4, 2022, 3:26am UTC](https://discourse.julialang.org/t/allow-3-argument-sum-in-julia/80457/5 "2022-05-04T03:26:00Z")

</div>

All of these alternatives are fine, I’m discussing this at the language level. If other people read my code (or even myself in the next month), they will get confused immediately.

---

<div class="post-metadata">

**Author:** ![rocco\_sprmnt21](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rocco_sprmnt21/32/20127_2.png) [@rocco\_sprmnt21](https://discourse.julialang.org/u/rocco_sprmnt21)\
**Post date:** [May 4, 2022, 4:10am UTC](https://discourse.julialang.org/t/allow-3-argument-sum-in-julia/80457/6 "2022-05-04T04:10:47Z")

</div>

?

```julia
sum(xor.(a,b))

```

---

<div class="post-metadata">

**Author:** ![Seif\_Shebl](https://avatars.discourse-cdn.com/v4/letter/s/eada6e/32.png) [@Seif\_Shebl](https://discourse.julialang.org/u/Seif_Shebl)\
**Post date:** [May 4, 2022, 4:12am UTC](https://discourse.julialang.org/t/allow-3-argument-sum-in-julia/80457/7 "2022-05-04T04:12:49Z")

</div>

This is less efficient as it has to allocate memory for the result of `xor` before it can sum.

---

<div class="post-metadata">

**Author:** ![rocco\_sprmnt21](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rocco_sprmnt21/32/20127_2.png) [@rocco\_sprmnt21](https://discourse.julialang.org/u/rocco_sprmnt21)\
**Post date:** [May 4, 2022, 4:20am UTC](https://discourse.julialang.org/t/allow-3-argument-sum-in-julia/80457/8 "2022-05-04T04:20:45Z")

</div>

```julia
sum(Base.splat(xor), zip(a,b))

```

---

<div class="post-metadata">

**Author:** ![tomerarnon](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tomerarnon/32/3170_2.png) [@tomerarnon](https://discourse.julialang.org/u/tomerarnon)\
**Post date:** [May 4, 2022, 4:20am UTC](https://discourse.julialang.org/t/allow-3-argument-sum-in-julia/80457/9 "2022-05-04T04:20:48Z")

</div>

Just to add a couple more options to the mix:

```julia
sum(x -> xor(x[1], x[2]), zip(a, b))
sum(x -> xor(x...), zip(a, b))
sum(((x, y),) -> xor(x, y), zip(a, b))
#or 
sum(x ⊻ y for (x,y) in zip(a,b))

```

> [@rocco\_sprmnt21](#):
>
> ```julia
> sum(xor.(a,b))
> 
> ```

The disadvantage here is that it allocates an intermediate array

All in all I think `sum` is pretty flexible already, and I find `mapreduce` to be quite readable after surprisingly little getting used to… That said, this definition

> [@photor](#):
>
> ```julia
> sum(f, a, b)=mapreduce(f, +, a, b)
> 
> ```

is pretty much exactly what it would need to be, since `sum(f, a)` already forwards to `mapreduce`. Probably

```julia
sum(f, iters...; kw...) = mapreduce(f, add_sum, iters...; kw...)

```

The primary reason _not_ to allow this is that the multi-argument version doesn’t mean the same thing at all as the single argument. `sum(a)` is the sum of the elements in `a`. Defining this method would naturally create `sum(a, b)` (since it would forward through `sum(identity, a , b)`), which is `sum(a) + sum(b)`.  
It’s weird to call that `sum(a, b)` I think…

---

<div class="post-metadata">

**Author:** ![Seif\_Shebl](https://avatars.discourse-cdn.com/v4/letter/s/eada6e/32.png) [@Seif\_Shebl](https://discourse.julialang.org/u/Seif_Shebl)\
**Post date:** [May 4, 2022, 4:47am UTC](https://discourse.julialang.org/t/allow-3-argument-sum-in-julia/80457/10 "2022-05-04T04:47:29Z")

</div>

> [@tomerarnon](#):
>
> That said, this definition

Exactly, I mean what prevents us from adding that one line to the definition of `sum`? For `sum(a, b)`, it shouldn’t work because for 2+ arguments the first must be a function. Also, `mapreduce(identity, +, a, b)` doesn’t work, so it should be `sum(+, a, b)` by the modified definition, because `mapreduce(+, +, a, b)` works.

I think the definition of 2 argument sum is here [https://github.com/JuliaLang/julia/blob/74a6589a32f0917c02817847b42dee9d4569bfca/base/reducedim.jl#L999](https://github.com/JuliaLang/julia/blob/74a6589a32f0917c02817847b42dee9d4569bfca/base/reducedim.jl#L999).

---

<div class="post-metadata">

**Author:** ![Seif\_Shebl](https://avatars.discourse-cdn.com/v4/letter/s/eada6e/32.png) [@Seif\_Shebl](https://discourse.julialang.org/u/Seif_Shebl)\
**Post date:** [May 4, 2022, 4:59am UTC](https://discourse.julialang.org/t/allow-3-argument-sum-in-julia/80457/11 "2022-05-04T04:59:19Z")

</div>

> [@photor](#):
>
> ```julia
> sum(f, a, b)=mapreduce(f, +, a, b)
> 
> ```

Yes, that’s all what it takes to allow the 3 arguments in the definition of `sum`. What prevents us from adding this line? I did some small experiments and it worked fine.

---

<div class="post-metadata">

**Author:** ![IlianPihlajamaa](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ilianpihlajamaa/32/28766_2.png) [@IlianPihlajamaa](https://discourse.julialang.org/u/IlianPihlajamaa)\
**Post date:** [May 4, 2022, 5:58am UTC](https://discourse.julialang.org/t/allow-3-argument-sum-in-julia/80457/12 "2022-05-04T05:58:37Z")

</div>

The reason against adding a method like that is not that it wouldn’t work, but it would make the language less readable for new users. When someone that doesn’t know Julia sees `sum(a, b, c)` they would expect the result to be `a+b+c` as mentioned before, since that is what the English word sum means.

Personally, I think that the method `sum(f, x) = sum(f.(x))` is already a bit of a stretch of the meaning of the word sum. If you had been suggesting to change that into `sum(x...)=sum(sum.(x))` such that `sum(a, b)` would mean `sum(a)+sum(b)`, I would probably agree with you, although this would be hugely breaking and will never be implemented for that reason. In the hypothetical scenario that it would be, a new convenience function `summap` could be defined that does `summap(f, x...) = sum(f.(x...))`, which does what you want.

---

<div class="post-metadata">

**Author:** ![albheim](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/albheim/32/34660_2.png) [@albheim](https://discourse.julialang.org/u/albheim)\
**Post date:** [May 4, 2022, 6:12am UTC](https://discourse.julialang.org/t/allow-3-argument-sum-in-julia/80457/13 "2022-05-04T06:12:24Z")

</div>

Did some quick benchmarking and got a bit surprised. I would have assumed that `mapreduce` was faster than `sum` + `zip`, but that seems to not be the case. Also surprised that the splat version is faster than the 2-arg version for the `sum`, the `@code_native` for them (the `sum` versions) was identical except for the line `movabsq $140681441048008, %rax # imm = 0x7FF2F351F5C8` which differed a little in the numbers, though I assume that is just memory adresses differing since the functions are different?

```julia
julia> f1(f, a, b) = mapreduce(f, +, a, b)
f1 (generic function with 1 method)

julia> f2(f, a, b) = sum(x -> f(x[1], x[2]), zip(a, b))
f2 (generic function with 1 method)

julia> f3(f, args...) = sum(Base.splat(f), zip(args...))
f3 (generic function with 1 method)

julia> @btime f1(xor, a, b) setup=(a=rand(Int, 1000); b=rand(Int, 1000))
  1.658 μs (5 allocations: 8.05 KiB)
4300509624842529229

julia> @btime f2(xor, a, b) setup=(a=rand(Int, 1000); b=rand(Int, 1000))
  599.045 ns (0 allocations: 0 bytes)
3772453359478653442

julia> @btime f3(xor, a, b) setup=(a=rand(Int, 1000); b=rand(Int, 1000))
  537.342 ns (0 allocations: 0 bytes)
4852733446304964671

```

---

<div class="post-metadata">

**Author:** ![Seif\_Shebl](https://avatars.discourse-cdn.com/v4/letter/s/eada6e/32.png) [@Seif\_Shebl](https://discourse.julialang.org/u/Seif_Shebl)\
**Post date:** [May 4, 2022, 6:47am UTC](https://discourse.julialang.org/t/allow-3-argument-sum-in-julia/80457/15 "2022-05-04T06:47:51Z")

</div>

You used `rand(Int,1000)` which sometimes overflows (negative output), instead of `rand(Bool, 1000)`. The following timings are more representative:

```julia
julia> @btime f1(xor, a, b) setup=(a=rand(Bool, 1000); b=rand(Bool, 1000))
  163.472 ns (1 allocation: 1.06 KiB)
519

julia> @btime f2(xor, a, b) setup=(a=rand(Bool, 1000); b=rand(Bool, 1000))
  551.337 ns (0 allocations: 0 bytes)
524

julia> @btime f3(xor, a, b) setup=(a=rand(Bool, 1000); b=rand(Bool, 1000))
  552.406 ns (0 allocations: 0 bytes)
475

```

---

<div class="post-metadata">

**Author:** ![DNF](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dnf/32/10191_2.png) [@DNF](https://discourse.julialang.org/u/DNF)\
**Post date:** [May 4, 2022, 6:50am UTC](https://discourse.julialang.org/t/allow-3-argument-sum-in-julia/80457/16 "2022-05-04T06:50:28Z")

</div>

One concern is that `sum` of vectors does pairwise summation. That’s apparently not the case for `sum` over a `zip`. But from a couple of quick tests (didn’t check the source code), it _does_ work for `mapreduce`:

```julia
julia> a=rand(10000); b = rand(10000);

julia> sum(max.(a, b)) - sum(Base.splat(max), zip(a, b))
4.547473508864641e-12

julia> sum(max.(a, b)) - mapreduce(max, +, a, b)
0.0

```

---

<div class="post-metadata">

**Author:** ![baggepinnen](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/baggepinnen/32/693_2.png) [@baggepinnen](https://discourse.julialang.org/u/baggepinnen)\
**Post date:** [May 4, 2022, 8:30am UTC](https://discourse.julialang.org/t/allow-3-argument-sum-in-julia/80457/17 "2022-05-04T08:30:02Z")

</div>

mapreduce is known to be slow, I think there are several issues open on it.

Regarding three-arg sum, see [Add function + two-argument method to reducers by baggepinnen · Pull Request #35017 · JuliaLang/julia · GitHub](https://github.com/JuliaLang/julia/pull/35017)

---

<div class="post-metadata">

**Author:** ![albheim](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/albheim/32/34660_2.png) [@albheim](https://discourse.julialang.org/u/albheim)\
**Post date:** [May 4, 2022, 9:10am UTC](https://discourse.julialang.org/t/allow-3-argument-sum-in-julia/80457/18 "2022-05-04T09:10:52Z")

</div>

Interesting that they behave that differently. Why would overflow affect them that disproportionally?

Why do you reckon that more representative? If you want to add the ability to sum over general functions and types, hopefully both cases should behave well for a good implementation of it?

It is visible for integers even when there is no overflow (still might be overflow checks?), e.g.

```julia
julia> @btime f1(xor, a, b) setup=(a=rand(1:10, 1000); b=rand(1:10, 1000))
  1.721 μs (5 allocations: 8.05 KiB)
6634

julia> @btime f2(xor, a, b) setup=(a=rand(1:10, 1000); b=rand(1:10, 1000))
  536.875 ns (0 allocations: 0 bytes)
6797

julia> @btime f3(xor, a, b) setup=(a=rand(1:10, 1000); b=rand(1:10, 1000))
  536.042 ns (0 allocations: 0 bytes)
6548

```

or when using `max` instead of `xor` it also looks the same to me.

I guess for some special cases `mapreduce` manages to be fast, but as @baggepinnen mentions, it looks like it can be slow in several other cases.
