izaban
izaban

Reputation: 1023

Variadic function with keyword arguments

I'm a newbie to Clojure and I was wondering if there is a way to define a function that can be called like this:

(strange-adder 1 2 3 :strange true)

That is, a function that can receive a variable number of ints and a keyword argument.

I know that I can define a function with keyword arguments this way:

(defn strange-adder
  [a b c & {:keys [strange]}]
  (println strange)
  (+ a b c))

But now my function can only receive a fixed number of ints.

Is there a way to use both styles at the same time?

Upvotes: 8

Views: 1255

Answers (3)

A. Webb
A. Webb

Reputation: 26446

Move the variadic portion to the the tail of the argument list and pass the options as a map:

(defn strange-adder [{:keys [strange]} & nums] 
  (println strange) 
  (apply + nums))

(strange-adder {:strange true} 1 2 3 4 5)

Upvotes: 4

noisesmith
noisesmith

Reputation: 20194

There is no formal support that I know of, but something like this should be doable:

(defn strange-adder
  [& args]
  (if (#{:strange} (-> args butlast last))
    (do (println (last args))
        (apply + (drop-last 2 args)))
    (apply + args)))

I don't know if this can be generalized (check for keywords? how to expand to an arbitrary number of final arguments?). One option may be putting all options in a hashmap as the final argument, and checking if the last argument is a hashmap (but this would not work for some functions that expect arbitrary arguments that could be hashmaps).

Upvotes: 1

Arthur Ulfeldt
Arthur Ulfeldt

Reputation: 91554

unfortunately, no.

The & destructuring operator uses everything after it on the argument list so it does not have the ability to handle two diferent sets of variable arity destructuring groups in one form.

one option is to break the function up into several arities. Though this only works if you can arrange it so only one of them is variadic (uses &). A more universal and less convenient solution is to treat the entire argument list as one variadic form, and pick the numbers off the start of it manually.

user> (defn strange-adder
        [& args]
         (let [nums (take-while number? args)
               opts (apply hash-map (drop-while number? args))
               strange (:strange opts)]
          (println strange)
              (apply + nums)))
#'user/strange-adder
user> (strange-adder 1 2 3 4 :strange 4)
4
10

Upvotes: 10

Related Questions