The question was
why and yours is the best attempt to answer
that, but I think, slightly off the mark.
Firstly, the fact that > is variadic doesn't negate the fact that
a single argument is a special case. It could quite easily return a
partial in that case and that case only. It doesn't buy you much
compared to doing it explicitly, but it
could.
If you don't believe it's a special case, consider what
(> 1
2 3) or
(> 3 2 1) are equivalent to. The first is
always false, no matter now many arguments you add, so it could
correctly evalutate to either
false, or
(fn ([] false)
([& args] false)) - putting the ambiguity of that to one
side for a moment. OTOH, the second form evaluates to either true,
or
(fn ([] true) ([& args] (apply > 1 args))). Again
there's a problem of ambiguity about which of those you might mean
when there are two or more arguments, but we can assume those would
never return a partial by default.
What this is intended to illustrate is that any list of multiple
arguments can either be collapsed to a single argument (the
rightmost one), or else the expression must simply be false. That
makes the one-argument form a special case because any
multi-argument form that you might want as a partial can be
expressed as a single-argument that resolves to the same thing.
However...
There is a much more serious downside to returning partials
implicitly. When considering < a and > as variadic functions
rather than binary operators, I prefer to think of them as
sorted-ascending? and sorted-descending? functions instead of
less-than and greater-than. This is both more technically correct
and makes reading code more intuitive than mentally expanding (>
a b c ...) to (a > b) && (b > c) && ....
If you think of them this way (and in fact, the docstring
defines
the functions this way, so you should), you ought to be able to pass
any list of arguments and get a sensible result that tells you
whether or not that list is in the desired order. By definition, a
list containing exactly one element must be in order, therefore the
operators must return true, as they do now.
So clearly, returning a partial instead of a result would result in
the loss of useful functionality. The only gain is not having to
type the word "partial" so it's not a great trade-off.
In writing this, I thought I'd better also test what (>) and
(<) evaluate to, because by the above definition, those should
also evaluate to true. Unfortunately, at least in v1.6, they throw
an arity error. IMO, by the same logic that says a single argument
is valid, no arguments should be valid too. Consider the following
perfectly valid use-case:
(def in-order? #(apply > %)) ; Seems obviously correct, no?
(def items []) ; Maybe get this from a database; might be empty
(in-order? items) ; Oops!
; ArityException Wrong number of args (0)...
This should perhaps be considered a bug. I suppose it depends on
your definition of "in order" for an empty set, but if we say that
the most sensible definition is based on whether the invariant
"sort(x)=x" holds true, then true is the correct result for the
empty set.
One final observations is that you could actually implement
something like this, which
does allow partial application
and yet also "just works" (mostly; caveat to follow):
(defn >>
([] (fn ([] true) ([& args] (apply > args))))
([x] (fn ([] true) ([& a] (apply > x a))))
([x & args] (if (apply > x args) (fn ([] true) ([& a] (apply > (last args) a))) false))
)
This returns either a partial, or false. This
almost works
perfectly. Because a function is not a falsey value in Clojure, you
can use the result of this as a predicate, or you can use it as a
partial application. However, it doesn't work when the return value
is false, because Clojure will not evaluate (false); it just throws
an exception. If (false) simply ignored its arguments and evaluated
to false, this code would work 100%. I'm sure there are Lisps out
there that would do that.
See how it works perfectly ...<brushes carpet>
apart
from that troublemaking (false) case...
(if (>>) true false) ; true
(if (>> 1) true false) ; true
(if (>> 2 1) true false) ; true
(if (>> 1 2) true false) ; false
((>>) 2 1) ; true
((>>) 1 2) ; false
((>> 1) 0) ; true
((>> 1) 2) ; false
((>> 2 1) 0) ; true
((>> 2 1) 3) ; false
((>> 1 2) 3) ; BOOM!
((>> 1 2) 0) ; BOOM!
I'm not suggesting that anyone actually try to use something like
this in a real project. It's a pretty dumb idea, but I thought it
was also mildly interesting. It might look cute but it's most likely
horribly inefficient and, let's be honest, probably a lot more
confusing than just typing the word "partial" when you want a
partial.
I'm also inclined to think the non-evaluation of (false) is probably
correct because the likelihood that it indicates a bug is probably a
lot higher than the number of cases where it might be convenient,
like this one.
- Robert