Here’s an issue I keep running into in Clojure:
user=> (max [3 4 5 6 7])
[3 4 5 6 7] ; expected '7'
Some functions don’t do what I expect!
Here’s one solution using apply:
user=> (apply max [3 4 5 6 7])
7
Other examples are concat, and min.
My question, as a Clojure newbie, is why are these functions variadic? I expected them to operate on sequences. Is using apply the best/idiomatic way to get what I want?
Note: I’m not trying to say that it’s bad to have variadic functions, or that there is a better way. I just want to know if there’s a rule or convention being followed, or if there are specific advantages to such an approach that I should be aware of.
Edit: I think the original question was unclear. Here’s what I meant:
In other programming languages I’ve used, there are monoid-like operations, such as adding numbers, finding the greater element, concatenating lists.
There are often two use cases for these operations:
1) combining two elements, using a function that accepts two arguments
2) combining 0 to n elements, using a function that accepts a list (or sequence) of elements
A function for the second case can be built from that for the first case (often using reduce).
However, Clojure adds a third use case:
3) combining 0 to n elements, using a variadic function
So the question is, why does Clojure add this third case?
Paul’s answer indicates that:
- this allows code that is more flexible
- there are historical forces at work
1) Convenience. With math functions such as
+it would be annoying to wrap everything in sequences when you just try to do some calculations.2) Efficiency. Wrapping everything with collections/sequences would also be inefficient, first the sequence needs to be created and then it needs to unpacked at runtime instead of looking up the right Java function at compile time.
3) Expectations. This is how these functions work in other Lisps and similarly, with a somewhat different syntax, in other functional languages, so it is a reasonable expectation for people coming to Clojure. I would say that the idiomatic way in other functional languages to apply a function such as
+to a sequence would be to use eitherreduceorfoldl/foldr, so this is also in line with how Clojure handles it.4) Flexibility. The fact that these functions may be used with higher order functions, such as
mapmakes them more convenient to use if they are variadic. Say you have three vectors and you want to apply a function to the elements at the same position. If your function is variadic then you can just use map with multiple collections (map also has to be variadic then 😉 ):This is a lot more convenient than what you would have if all those functions just took collections.
Idiomatic use: (Edited after kotarak’s excellent comment)
It depends on the function if you should use
reduceorapply.For math functions (
+,-,*,/,etc.)that take 2 arguments in the Java worldreducemakes more sense as it can directly use the 2 argument Java version. Withapplythey kind of do an implicitreduce(the function adds two arguments and then recurs with the result, the next argument and the rest. That’s pretty much what reduce does.)For
strusingapplyis likely more efficient. Whenstris called with more than one argument it creates a StringBuilder, adds all the arguments to it and then creates a string. Usingreducethe StringBuilder will be created n-1 times and only add one string each time. This is as in the Shlemiel the painter joke, resulting in O(n^2) complexity.Verdict so far: Using
applywith math functions doesn’t hurt much, but usingreducewithstrmay be pretty expensive.