Clojure's min-key
and max-key
operators are variants of min
and max
that minimize or maximize some function of their arguments:
user=> (max-key count "Find" "the" "longest" "word")
"longest"
I used min-key
a few times recently, and found it awkward every time, because the input arrived in a list, not in several separate expressions. Converting from one to the other is a simple matter of apply
, but the resulting expression is not as clear as it ought to be:
user=> (apply min-key count
(clojure.string/split "Find the shortest word" #" "))
"the"
It seems to me that while min
and max
are almost always used on separate arguments, min-key
and max-key
are much more likely to be used on lists. (In general, I think high-order functions are much more likely to be used on lists than their first-order relatives.) Googling clojure min-key supports this: almost all uses are (apply min-key ...)
, not (min-key ...)
— and all of the latter seem to be either artificial examples or mistakes. Even the spelling corrector which was the original motivation for adding max-key
uses it with apply
. So these functions should really be provided in list form (like Arc's most
) rather than separate-argument form.
This is a general problem, though. Any operator that takes an arbitrary number of arguments will probably be used sometimes on lists and sometimes on a few explicit arguments. apply
or reduce
are the obvious ways to transform one into the other, but they're rather disappointing when you're expecting a clear, convenient list function.
(Functions that aren't useful with only one argument (like min-key
) can be overloaded to provide both: (min-key f xs)
could operate on the elements of xs
, while (min-key f a b)
operates on a
and b
. This is what Python does. This is a potentially susprising irregularity, though ((min-key f a)
no longer does what you expect), and it doesn't work for functions that can usefully take one argument. So I don't think it's a good idea.)
Also stumbled accross the "apply problem" a lot of times. I also have a lot of functions that I need in both ways, so I use foo for the variadic and foo* for the list function. But I'd like to find a better solution...
ReplyDeletePossibly an argument for disallowing variadic functions, and using lists instead. Such problems would go away.
ReplyDeleteHeck, you could even make the normal variadic call become sugar. Like:
(some-function [1 2 3])
vs.
(some-function | 1 2 3).