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.
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:
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].
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
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:
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.
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:
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.
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.
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.
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.