1

Here is the implementation of the Clojure's complement function:

(defn complement
  "Takes a fn f and returns a fn that takes the same arguments as f,
  has the same effects, if any, and returns the opposite truth value."
  {:added "1.0"
   :static true}
  [f]
  (fn
    ([] (not (f)))
    ([x] (not (f x)))
    ([x y] (not (f x y)))
    ([x y & zs] (not (apply f x y zs)))))

Why is it defined as a multi-arity function? The following implementation, it seems to me, would achieve the same result:

(defn alternative-complement [f]
  (fn [& args] (not (apply f args))))

For what reason, does the Clojure's complement treat none, single and two arguments as "special cases"?

Tim
  • 12,318
  • 7
  • 50
  • 72
  • 2
    Maybe because it is easier for the compiler to optimize the specific cases of 0, 1 and 2 arguments? Thereby avoiding the runtime overhead of working with a sequence. – Rulle Oct 15 '21 at 08:35
  • 1
    Exactly for that reason. There are many other examples of such an approach in `clojure.core`. – Eugene Pakhomov Oct 15 '21 at 09:04
  • 1
    It was added [here](https://github.com/clojure/clojure/commit/b8e333fb3437dca760f16136ed074a4dd463fe35#diff-313df99869bda118262a387b322021d2cd9bea959fad3890cb78c6c37b448299L838-R855) and it is to be expected, that it is an optimization. – cfrick Oct 15 '21 at 09:24
  • 1
    I'm sure this question has been asked before here, but I can't find it. https://stackoverflow.com/questions/10769005/functions-overloaded-with-different-number-of-arguments is close. – amalloy Oct 15 '21 at 09:41

1 Answers1

1

Under the hood, when you use Java's variable arguments feature, the compiler creates an array for the arguments. There is a performance penalty associated with this array creation.

In order to eliminate this performance penalty, specific arities are added for the most commonly used cases, optimizing for these. If it can be determined through some means that complement is used on functions which take zero, one or two arguments, most of the time, then it's deemed worth the effort to add specific arities for zero, one, and two argument functions. Functions which take more than two arguments are supported but may incur a minor performance penalty.

Java's own core library makes use method overloading to eliminate the performance hit of varargs. See Java 11's List which contains twelve overloads for the static of method, maxing out at allowing ten elements.

static <E> List<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9, E e10) {
    return ImmutableCollections.listFromTrustedArray(e1, e2, e3, e4, e5,
                                                         e6, e7, e8, e9, e10);
}
Josh
  • 4,726
  • 2
  • 20
  • 32