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:
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 either reduce
or foldl
/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 map
makes 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 ;) ):
(map + [1 2 3 4] [2 3 4 5] [3 4 5 6])
; [6 9 12 15]
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 reduce
or apply
.
For math functions (+,-,*,/,etc.)
that take 2 arguments in the Java world reduce
makes more sense as it can directly use the 2 argument Java version. With apply
they kind of do an implicit reduce
(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 str
using apply
is likely more efficient. When str
is called with more than one argument it creates a StringBuilder, adds all the arguments to it and then creates a string. Using reduce
the 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 apply
with math functions doesn't hurt much, but using reduce
with str
may be pretty expensive.
Alex Miller just wrote about a related issue in his blog: 2 is a smell
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With