min-key and max-key should take lists

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.)

2 comments:

  1. 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...

    ReplyDelete
  2. Possibly an argument for disallowing variadic functions, and using lists instead. Such problems would go away.

    Heck, you could even make the normal variadic call become sugar. Like:

    (some-function [1 2 3])

    vs.

    (some-function | 1 2 3).

    ReplyDelete

It's OK to comment on old posts.