Another Clojure don’t today. This one is a personal style preference, but I’ll try to back it up.
Say you want to define a function with a mix of required and optional arguments. I’ve often seen this:
(defn foo [a & [b]] (println "Required argument a is" a) (println "Optional argument b is" b))
This is a clever trick. It works because
& [b] destructures the sequence of arguments passed to the function after
a. Sequential destructuring doesn’t require that the number of symbols match the number of elements in the sequence being bound. If there are more symbols than values, they are bound to
(foo 3 4) ;; Required argument a is 3 ;; Optional argument b is 4 ;;=> nil (foo 9) ;; Required argument a is 9 ;; Optional argument b is nil ;;=> nil
I don’t like this pattern for two reasons.
One. Because it’s variable arity, the function
foo accepts any number of arguments. You won’t get an error if you call it with extra arguments, they will just be silently ignored.
(foo 5 6 7 8) ;; Required argument a is 5 ;; Optional argument b is 6 ;;=> nil
Two. It muddles the intent. The presence of
& in the parameter vector suggests that this function is meant to be variable-arity. Reading this code, I might start to wonder why. Or I might miss the
& and think this function is meant to be called with a sequence as its second argument.
A couple more lines make it clearer:
(defn foo ([a] (foo a nil)) ([a b] (println "Required argument a is" a) (println "Optional argument b is" b)))
The intent here is unambiguous: The function takes either one or two arguments, with
b defaulting to
nil. Trying to call it with more than two arguments will throw an exception, telling you that you did something wrong.
And one more thing: it’s faster. Variable-arity function calls have to allocate a sequence to hold the arguments, then go through
apply. Timothy Baldridge did a quick performance comparison showing that calls to a function with multiple, fixed arities can be much faster than variable-arity (varargs) function calls.