Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

functions overloaded with different number of arguments

A lot of the Clojure functions accept various number of arguments and I'm often a bit confused by the documentation and how it relates to the way I should be using the function.

For example (doc partial) returns this:

([f arg1] [f arg1 arg2] [f arg1 arg2 arg3] [f arg1 arg2 arg3 & more])

My question is not specifically about partial but...

Why up to arg1 arg2 arg3 & more and not arg1 & more or arg1 arg2 & more or arg1 arg2 arg3 arg4 arg5 arg6 arg7 arg8 & more ?

I'm being facetious with the last one but this is a very real question: what determines how many "argX" one needs to put before the & more?

like image 219
Cedric Martin Avatar asked Dec 28 '22 00:12

Cedric Martin


2 Answers

The following answer is a guess on my part: A look at the implementation of partial show us:

(defn partial
  "Takes a function f and fewer than the normal arguments to f, and
  returns a fn that takes a variable number of additional args. When
  called, the returned function calls f with args + additional args."
  {:added "1.0"
   :static true}
  ([f arg1]
   (fn [& args] (apply f arg1 args)))
  ([f arg1 arg2]
   (fn [& args] (apply f arg1 arg2 args)))
  ([f arg1 arg2 arg3]
   (fn [& args] (apply f arg1 arg2 arg3 args)))
  ([f arg1 arg2 arg3 & more]
   (fn [& args] (apply f arg1 arg2 arg3 (concat more args)))))

As you can see, every call to partial is doing the same thing - namely, returning a function which accepts some args and calls apply on the input function with input args and new args. So this could indeed have been written as arg1 & more. But wait, lets look at the implementation of apply as well:

(defn apply
 "Applies fn f to the argument list formed by prepending intervening arguments to args."
 {:added "1.0"
  :static true}
 ([^clojure.lang.IFn f args]
    (. f (applyTo (seq args))))
 ([^clojure.lang.IFn f x args]
    (. f (applyTo (list* x args))))
 ([^clojure.lang.IFn f x y args]
    (. f (applyTo (list* x y args))))
 ([^clojure.lang.IFn f x y z args]
    (. f (applyTo (list* x y z args))))
 ([^clojure.lang.IFn f a b c d & args]
    (. f (applyTo (cons a (cons b (cons c (cons d (spread args)))))))))

Apply is a core function, and it is executes differently when given a different number of arguments. This is an optimization to apply for performance reasons. This is the reason for exposing different arities of partial (and other such functions), because the internal execution of the code differs for different arities.

I'm assuming that the clojure/core team thought that exposing the arity of partial beyond arg1 arg2 arg3 & more (ie writing arg1 arg2 arg3 arg4 & more) would not be aesthetic, so they decided to stop at 3 args & more.

like image 157
vedang Avatar answered Jan 05 '23 16:01

vedang


Whatever you think is reasonable. Its going to be faster to call a function if its not of variable arity, so they provide several that will be called directly if you don't exceed their limit.

I think there's also a limit in IFn of 20(?) args to a function, but I don't know if there's a work around in the compiler for that or not.

like image 21
Bill Avatar answered Jan 05 '23 16:01

Bill