# Add varargs methods of \`any\`, \`all\`, and \`count\`

**URL:** <https://discourse.julialang.org/t/add-varargs-methods-of-any-all-and-count/95072>\
**Category:** Internals & Design\
**Tags:** count, all, any, varargs\
**Created:** [February 23, 2023, 11:13am UTC](https://discourse.julialang.org/t/add-varargs-methods-of-any-all-and-count/95072 "2023-02-23T11:13:07Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![uniment](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/uniment/32/24532_2.png) [@uniment](https://discourse.julialang.org/u/uniment)\
**Post date:** [February 23, 2023, 11:13am UTC](https://discourse.julialang.org/t/add-varargs-methods-of-any-all-and-count/95072/1 "2023-02-23T11:13:07Z")

</div>

It would be helpful to have variable-length methods of `any`, `all`, and `count` for comparing collections, so that e.g. you could write this:

```julia
all(≡, [1,2,3], [1,2,3]) # true
count(≥, (3,2,1), 1:3) # 2

```

Proof of concept:

```julia
Base.any(f::Function, c1, c2, cs...) = any(splat(f), zip(c1, c2, cs...))
Base.all(f::Function, c1, c2, cs...) = all(splat(f), zip(c1, c2, cs...))
Base.count(f::Function, c1, c2, cs...) = count(splat(f), zip(c1, c2, cs...))

```

~~This method of `all` might be nicer without calling `length`, but I haven’t yet dug into the best of way of doing that. I’m also not sure whether it’s better for `all` to return `false` when collection lengths mismatch, or to throw an error.~~

Thoughts?

---

<div class="post-metadata">

**Author:** ![barucden](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/barucden/32/26154_2.png) [@barucden](https://discourse.julialang.org/u/barucden)\
**Post date:** [February 23, 2023, 12:34pm UTC](https://discourse.julialang.org/t/add-varargs-methods-of-any-all-and-count/95072/2 "2023-02-23T12:34:52Z")

</div>

It does not feel very natural to me. I would better understand the simple

```julia
count(t -> t[1] ≥ t[2], zip((3,2,1), 1:3))

```

and if that’s too ugly, then be explicit about the splatting (which is the POC implementation in OP):

```julia
f = (a, b) -> a ≥ b
count(Base.splat(f), zip((3,2,1), 1:3))

```

I can understand this code immediately, whereas I have to think about what `count(≥, (3,2,1), 1:3)` means.

Overall, I don’t see much added value, given the added complexity. But that can just be me, I am generally conservative about the new features.

---

<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:** [February 23, 2023, 1:16pm UTC](https://discourse.julialang.org/t/add-varargs-methods-of-any-all-and-count/95072/3 "2023-02-23T13:16:54Z")

</div>

> [@barucden](#):
>
> I would better understand the simple

Here’s a vote in the opposite direction. I find

```julia
count(≥, (3,2,1), 1:3)

```

_significantly_ more clear and obvious than

```julia
count(t -> t[1] ≥ t[2], zip((3,2,1), 1:3))

```

and _dramatically_ clearer than

```julia
f = (a, b) -> a ≥ b
count(Base.splat(f), zip((3,2,1), 1:3))

```

That latter can be simplified, though, to this:

```julia
count(Base.splat(>=), zip((3,2,1), 1:3))

```

Still, anything involving the `splat` function feels a bit mind-bending.

Having vararg methods for `any`, `all`, and `count` dovetails nicely with `map`. IMO. If you can parse

```julia
map(>, [3,2,1], 1:3)

```

then it’s not hard to see what

```julia
any(>, [3,2,1], 1:3)

```

does.

---

<div class="post-metadata">

**Author:** ![barucden](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/barucden/32/26154_2.png) [@barucden](https://discourse.julialang.org/u/barucden)\
**Post date:** [February 23, 2023, 1:37pm UTC](https://discourse.julialang.org/t/add-varargs-methods-of-any-all-and-count/95072/4 "2023-02-23T13:37:44Z")

</div>

Yes, I think it is to some extent a matter of personal preference. I was not trying to disprove other people’s opinions.

I guess this might be a better example:

```julia
a = (2, 2)
b = (1, 2)
count(≥, a, b)

```

It is not obvious to me that this should return `2` since `a[1] ≥ b[1]` and `a[2] ≥ b[2]`. I could imagine a different semantics where this returns `1` since `a[1] ≥ a[2]` and `b[1] < b[2]`.

> [@DNF](#):
>
> If you can parse
> 
> ```julia
> map(>, [3,2,1], 1:3)
> 
> ```

I was not aware this existed. I have the same problem with it as I sketched above though. But I guess I am late to the party and should have objected when this `map` method was discussed 🙂

---

<div class="post-metadata">

**Author:** ![uniment](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/uniment/32/24532_2.png) [@uniment](https://discourse.julialang.org/u/uniment)\
**Post date:** [February 23, 2023, 9:25pm UTC](https://discourse.julialang.org/t/add-varargs-methods-of-any-all-and-count/95072/5 "2023-02-23T21:25:04Z")

</div>

> [@barucden](#):
>
> example:
> 
> ```julia
> a = (2, 2)
> b = (1, 2)
> count(≥, a, b)
> 
> ```

> imagine a different semantics where this returns `1` since `a[1] ≥ a[2]` and `b[1] < b[2]`

This is a bit like the difference between `max` and `maximum`: you can write `maximum(f, c)` as `mapreduce(f, max, c)`. While `max` acts on the objects in its arguments directly, and is thus a useful comparison operator, `maximum` reduces a _collection_ of objects.

It should be clear that the current behavior of `count` is closer to `maximum` than `max`, as `count` acts on a collection. We could also write:

```julia
count(f, c) = mapreduce(x->f(x)::Bool, +, c; init=0)
any(f, c) = mapreduce(x->f(x)::Bool, |, c; init=false)
all(f, c) = mapreduce(x->f(x)::Bool, &, c; init=true)

```

which makes these relationships clear. Thus, (#1) the OP is more consistent with the current definition.

Additionally, (#2) if the varargs methods behaved as you imagine, it would create an ambiguity with the current definition. In the case of `count(≥, (2,2))`, should it throw an error as it does now, or should it return `1`? Should `count(≥(2), (2,2))` return `2` as it does now, or should it throw an error?

There’s also (#3) an argument to be made for efficiency. For a large collection of objects `c`, it turns out that calling `maximum(c)` is more efficient than calling `max(c...)`, as the latter will require compiling a large number of methods. A similar argument applies here: if the size of the collection will be greater than about 31, splatting it across the function’s arguments becomes expensive.

And finally, (#4) the semantics proposed in the OP are consistent with those of varargs `map` and `mapreduce`.

So for these four reasons, I think the semantics of the OP make more sense, even though it does raise the question of what `all` should do for collections of mismatched lengths.

---

<div class="post-metadata">

**Author:** ![uniment](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/uniment/32/24532_2.png) [@uniment](https://discourse.julialang.org/u/uniment)\
**Post date:** [February 23, 2023, 9:43pm UTC](https://discourse.julialang.org/t/add-varargs-methods-of-any-all-and-count/95072/6 "2023-02-23T21:43:54Z")

</div>

Sidenote…

I don’t know what I expected, but it wasn’t this 😅

```julia
julia> all(==(0), Iterators.repeated(0))
false

```

It seems disjoint from this behavior:

```julia
all(==(()), zip())

```

---

<div class="post-metadata">

**Author:** ![jar1](https://avatars.discourse-cdn.com/v4/letter/j/c0e974/32.png) [@jar1](https://discourse.julialang.org/u/jar1)\
**Post date:** [February 23, 2023, 11:44pm UTC](https://discourse.julialang.org/t/add-varargs-methods-of-any-all-and-count/95072/7 "2023-02-23T23:44:31Z")

</div>

That’s UB, one of these:

> <https://github.com/JuliaLang/julia/issues/40009#issuecomment-1324234577>
>
> Our longstanding policy on forward progress guarantees has basically been that
> …we expose the LLVM behavior and follow the C/C++ world by considering infinite
> loops to be undefined behavior, leading to behavior like:
> \`\`\`
> julia\> f(x) = x ? true : while true; end
> f (generic function with 1 method)
> 
> julia\> f(true)
> true
> 
> julia\> f(false)
> true
> \`\`\`
> 
> This might not seem to bad, but it can lead to unsoundness and security vulnerabilities ("nice error check you have there, would be a shame if somebody were to see the UB before it")
> \`\`\`
> julia\> @noinline function my\_checkbounds(Alen::Int, i::Int)
> if i \< 0 || i \> Alen
> while true; end # A bad error path, maybe inlined from somewhere
> return false
> end
> return true
> end
> my\_checkbounds (generic function with 2 methods)
> 
> julia\> function my\_getindex(A::Vector{Int}, i::Int)
> my\_checkbounds(length(A), i) || error("Out of bounds!")
> @inbounds A\[i\]
> end
> my\_getindex (generic function with 1 method)
> 
> julia\> my\_getindex(Int\[1\], 100000000)
> 
> signal (11): Segmentation fault
> \`\`\`
> 
> For this reason, more modern languages tend to not make forward progress assumptions. The Rust folks in particular took this quite seriously and recently changed LLVM to require the \`mustprogress\` attribute in order to top into forward progress guarantees (https://reviews.llvm.org/D85393). We should carefully chose what to do here. Since we don't set the new LLVM attribute, LLVM actually no longer makes these forward progress assumptions on our code. However, we do still make such assumptions in our own inference code.
> 
> Now that LLVM is fixed, my preference would be to follow Rust here and stop making any forward progress assumptions.

---

<div class="post-metadata">

**Author:** ![uniment](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/uniment/32/24532_2.png) [@uniment](https://discourse.julialang.org/u/uniment)\
**Post date:** [February 24, 2023, 3:48am UTC](https://discourse.julialang.org/t/add-varargs-methods-of-any-all-and-count/95072/8 "2023-02-24T03:48:25Z")

</div>

On further thought, it seems like what’s the best thing for `all` to do, when iterators are of different lengths, is to terminate iteration on the first terminating iterator (i.e., the same behavior as `zip` and `map`). I’ve updated the OP accordingly.

Why? For the same reason as `zip`: some iterators go on forever:

```julia
julia> Base.all(f::Function, c1, c2, cs...) = all(splat(f), zip(c1, c2, cs...))
       mod3 = Iterators.cycle(0:2);
       all(==, mod3, [0,1,2,0,1,2])
true

```

If the length of all the collections must be equal, then it makes sense for the user to specify that separately.

---

<div class="post-metadata">

**Author:** ![Jean\_Michel](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jean_michel/32/8282_2.png) [@Jean\_Michel](https://discourse.julialang.org/u/Jean_Michel)\
**Post date:** [February 24, 2023, 10:07am UTC](https://discourse.julialang.org/t/add-varargs-methods-of-any-all-and-count/95072/9 "2023-02-24T10:07:27Z")

</div>

But you have

```julia
julia> all(==(0), Iterators.repeated(0,0))
true

```

---

<div class="post-metadata">

**Author:** ![benninkrs](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/benninkrs/32/3191_2.png) [@benninkrs](https://discourse.julialang.org/u/benninkrs)\
**Post date:** [February 25, 2023, 12:10am UTC](https://discourse.julialang.org/t/add-varargs-methods-of-any-all-and-count/95072/10 "2023-02-25T00:10:33Z")

</div>

I must confess I don’t understand the motivation for the OP. Julia already supports this:

```julia
julia> all([1,2,3] .== 1:3)
true

julia> count((3,2,1) .>= 1:3)
2  

```

which is clear and convenient.

---

<div class="post-metadata">

**Author:** ![uniment](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/uniment/32/24532_2.png) [@uniment](https://discourse.julialang.org/u/uniment)\
**Post date:** [February 25, 2023, 12:22am UTC](https://discourse.julialang.org/t/add-varargs-methods-of-any-all-and-count/95072/11 "2023-02-25T00:22:06Z")

</div>

```julia
julia> using BenchmarkTools
       Base.all(f::Function, c1, c2, cs...) = all(splat(f), zip(c1, c2, cs...))
       @btime all(a .== b) setup=(a=[1,2,3]; b=1:3)
       @btime all(==, a, b) setup=(a=[1,2,3]; b=1:3)
  42.553 ns (2 allocations: 96 bytes)
  3.700 ns (0 allocations: 0 bytes)
true

```

If a language is going to call itself performant, then its easiest and most natural idioms should be those that avoid performance traps.

---

<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:** [February 25, 2023, 12:37am UTC](https://discourse.julialang.org/t/add-varargs-methods-of-any-all-and-count/95072/12 "2023-02-25T00:37:52Z")

</div>

> [@benninkrs](#):
>
> ```julia
> julia> all([1,2,3] .== 1:3)
> true
> 
> ```

I would never write something like this.

---

<div class="post-metadata">

**Author:** ![lmiq](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lmiq/32/18314_2.png) [@lmiq](https://discourse.julialang.org/u/lmiq)\
**Post date:** [February 25, 2023, 12:44am UTC](https://discourse.julialang.org/t/add-varargs-methods-of-any-all-and-count/95072/13 "2023-02-25T00:44:29Z")

</div>

> [@uniment](#):
>
> its easiest and most natural idioms

One could argue that `all(a .== b)` is the easiest and most natural. We can also hope that escape analysis can avoid the intermediate here if the code is inside a function. At the global scope it is not avoidable since the argument is parsed first.

But anyway I like your proposal since it is the way this problem is handled everywhere in Julia.

---

<div class="post-metadata">

**Author:** ![benninkrs](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/benninkrs/32/3191_2.png) [@benninkrs](https://discourse.julialang.org/u/benninkrs)\
**Post date:** [February 25, 2023, 12:50am UTC](https://discourse.julialang.org/t/add-varargs-methods-of-any-all-and-count/95072/14 "2023-02-25T00:50:55Z")

</div>

> its easiest and most natural idioms should be those that avoid performance traps

Sure. The OP gave the impression the problem was lack of convenient syntax, not performance. As shown in your post above, `mapreduce` already does the job, but your proposed syntax is admittedly prettier. I wonder how many other collection-reducing functions there are that would benefit from such syntax? There can be such a thing as _too_ many convenience methods, but `any` and `all` are two of the more important functions.

---

<div class="post-metadata">

**Author:** ![uniment](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/uniment/32/24532_2.png) [@uniment](https://discourse.julialang.org/u/uniment)\
**Post date:** [February 25, 2023, 2:36am UTC](https://discourse.julialang.org/t/add-varargs-methods-of-any-all-and-count/95072/15 "2023-02-25T02:36:33Z")

</div>

> [@lmiq](#):
>
> One could argue that `all(a .== b)` is the easiest and most natural.

How much better would the world be if broadcasting returned a lazy collection?

```julia
julia> [1, 2, 3] .== 1:3
3-element BroadcastingVector{Bool, Tuple{typeof(==), Vector{Int64}, UnitRange{Int64}}}:
 1
 1
 1

```

> [@benninkrs](#):
>
> There can be such a thing as _too_ many convenience methods, but `any` and `all` are two of the more important functions.

“All things are poison and nothing is without poison. Solely the dose determines that a thing is not a poison” - Paracelsus

---

<div class="post-metadata">

**Author:** ![jar1](https://avatars.discourse-cdn.com/v4/letter/j/c0e974/32.png) [@jar1](https://discourse.julialang.org/u/jar1)\
**Post date:** [February 25, 2023, 2:45am UTC](https://discourse.julialang.org/t/add-varargs-methods-of-any-all-and-count/95072/16 "2023-02-25T02:45:17Z")

</div>

I don’t think `any` and `all` are so special. If they’re gonna have variadic methods then so should many other reductions. I’m not convinced that’s worth the trouble.

---

<div class="post-metadata">

**Author:** ![uniment](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/uniment/32/24532_2.png) [@uniment](https://discourse.julialang.org/u/uniment)\
**Post date:** [February 25, 2023, 3:07am UTC](https://discourse.julialang.org/t/add-varargs-methods-of-any-all-and-count/95072/17 "2023-02-25T03:07:00Z")

</div>

> [@jar1](#):
>
> If they’re gonna have variadic methods then so should many other reductions.

What do you have in mind?

---

<div class="post-metadata">

**Author:** ![jar1](https://avatars.discourse-cdn.com/v4/letter/j/c0e974/32.png) [@jar1](https://discourse.julialang.org/u/jar1)\
**Post date:** [February 25, 2023, 3:15am UTC](https://discourse.julialang.org/t/add-varargs-methods-of-any-all-and-count/95072/18 "2023-02-25T03:15:24Z")

</div>

minimum, maximum (which really make any/all unnecessary), extrema, findmin, findmax, argmin, …

---

<div class="post-metadata">

**Author:** ![uniment](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/uniment/32/24532_2.png) [@uniment](https://discourse.julialang.org/u/uniment)\
**Post date:** [February 25, 2023, 4:31am UTC](https://discourse.julialang.org/t/add-varargs-methods-of-any-all-and-count/95072/19 "2023-02-25T04:31:19Z")

</div>

I can see how vararg `minimum` and `maximum`, if they existed, could be trickily punned into serving the role of vararg `any` and `all`, but I struggle to see how any of the items on the list {minimum, maximum, extrema, findmin, findmax, argmin, argmax} would actually “make sense,” conceptually/semantically speaking, with vararg methods. Unlike any of those, `count`, `any`, and `all` take _logical predicates_, which can be meaningfully applied to a collection of collections; I’m not sure I can find an agreeable way for the “maximum” of a collection of collections to be meaningful.

That said, we do find {findfirst, findlast, findnext} which take logical predicates—and there we find conflict because of `findnext(pred::Function, A, i)`. I would point out, though, that {findfirst, findlast, findnext} are expected to return the _index of the collection_, which isn’t generally a meaningful concept for a vararg method that will accept multiple collections.

So, I don’t think that the slippery slope argument here poses a real challenge; there doesn’t seem to me any reason to extend vararg methods to these other reducing functions.

---

<div class="post-metadata">

**Author:** ![Mason](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mason/32/2423_2.png) [@Mason](https://discourse.julialang.org/u/Mason)\
**Post date:** [February 25, 2023, 4:49am UTC](https://discourse.julialang.org/t/add-varargs-methods-of-any-all-and-count/95072/20 "2023-02-25T04:49:21Z")

</div>

> [@uniment](#):
>
> How much better would the world be if broadcasting returned a lazy collection?

```julia
julia> using LazyArrays: @~

julia> using BenchmarkTools
       Base.all(f, c1, c2, cs...) = all(splat(f), zip(c1, c2, cs...))
       @btime all(a .== b) setup=(a=[1,2,3]; b=1:3)
       @btime all(==, a, b) setup=(a=[1,2,3]; b=1:3)
       @btime all(@~ a .== b) setup=(a=[1,2,3]; b=1:3)
  28.533 ns (2 allocations: 96 bytes)
  2.809 ns (0 allocations: 0 bytes)
  5.169 ns (0 allocations: 0 bytes)
true

```

[Next page](https://discourse.julialang.org/t/add-varargs-methods-of-any-all-and-count/95072.md?page=2)
