Anthony
Anthony

Reputation: 2050

How to pass the rest args of a variadic function to another function?

I want to write a function which simply updates a vector in a map with new value, but can take any number of args, but at least one.

Here is example:

(defn my-update [what item & items]
  (update what :desired-key conj item items))

Unfortunately, this doesn't work. Despite that update do have a signature with multiple values (like [m k f x y]), all remaining arguments to my-update will be joined into one sequence, which will be passed to conj as one argument.

Instead, wrapping conj with apply in an anonymous function does work, but looks not so elegant:

(defn my-update [what item & items]
  (update what :desired-key #(apply conj % item items))

What is the idiomatic way of writing such a function like my-update?

Upvotes: 1

Views: 558

Answers (2)

Rulle
Rulle

Reputation: 4901

You can simply insert apply before update. That will call the function update with the arguments that follows except for the last argument which should be a sequence, whose elements become the remaining arguments in the call:

(defn my-update [what item & items]
  (apply update what :desired-key conj item items))

(my-update {:desired-key [0]} 1 2 3 4)
;; => {:desired-key [0 1 2 3 4]}

(my-update {:desired-key [0]})
;; Exception: Wrong number of args (1) passed to: my-update

This way, you can keep the function argument list [what item & items] that makes it clear that at least one item needs to be provided.

In general, a call (apply f a b c ... [x y z ...]) will evaluate to the same as (f a b c ... x y z ...).

Upvotes: 4

Alan Thompson
Alan Thompson

Reputation: 29958

Your existing solution isn't so bad. One small improvement is to use the into function, which uses conj internally to join two sequences together:

(defn my-update [what & items]
  (update what :a into items))

with result

(my-update {:a [1]} 2 3 4) => {:a [1 2 3 4]}

Another alternative is to extract out the anonymous function into a named function:

(defn append-to-seq
  [seq item items]
  (-> (vec seq)  ; ensure it is a vector so conj adds to the end, not beginning
    (conj item)
    (into items)))

(defn my-update [what item & items]
  (update what :a append-to-seq item items))


Upvotes: 1

Related Questions