Kai
Kai

Reputation: 9552

Clojure Parameters with Optional Flags

What's the best way to implement keywords as optional flags to a function? I want to make function calls such as:

(myfunction 5)
(myfunction 6 :do-this)
(myfunction 3 :go-here)
(myfunction 2 :do-this :do-that)

Using defn, I can define a function such as:

(defn myfunction [value & flags] ... )

But the flags becomes a list. I can write my own function to search the list, but such a function isn't included in the core library, so I assume it's not idiomatic.

What I'm using now:

(defn flag-set? [list flag] (not (empty? (filter #(= flag %) list))))
(defn flag-add [list flag] (cons flag list))
(defn flag-remove [list flag] (filter #(not= flag %) list))

Upvotes: 15

Views: 6112

Answers (5)

David Ongaro
David Ongaro

Reputation: 3937

Converting the rest argument list into a set just to check for membership can be considered clumsy. Using some with a static set for that purpose is considered idiomatic though. So you could do something like this

(defn myfunction [value & flags]
  (if (some #{:do-this} flags)
    [value flags]))

(myfunction 123 :do-this)
-> [123 (:do-this)]

An alternative could be using keyword arguments which were introduced in Clojure 1.2. E.g.

(defn myfunction [value & {:keys [do-this]}]
  (if do-this
    [value do-this]))

(myfunction 123 :do-this true)
-> [123 true]

This adds the "burden" to the caller to provide an explicit true value for the flag, but the function signature becomes arguably less obscure and self-documenting (since a name like flags is rather abstract). So I suppose it's a tradeoff.

Upvotes: 0

Daniel Dinnyes
Daniel Dinnyes

Reputation: 5017

You can use hash-map binding for destructuring of optional parameters like this:

(defn myfunction 
  [value & {:keys [go-there do-this do-that times] :or {times 1}}]
  {:pre [(integer? times) (< 0 times)]}
  (println "Saw a" value)
  (when go-there
    (dotimes [n times]
      (when do-this (println "Did THIS with" value))
      (when do-that (println "Did THAT with" value)))))

The above function may be called the following way:

(myfunction "foo" :go-there true :do-this true :do-that false :times 5)

Notice that you can define default values for keys with the :or {times 1} clause. The following function call will only loop once due to that default:

(myfunction "foo" :go-there true :do-this true :do-that false)

Also, Clojure's precondition expressions allow for convenient testing of parameters, which applies to the values of the destructured keys too, as it can be seen in the {:pre [...]} expression right after the parameter bindings. The following call will fail due that precondition check:

(myfunction "foo" :go-there true :do-this true :do-that false :times -1)

Upvotes: 1

pmf
pmf

Reputation: 7749

clojure.contrib.def includes the defnk-macro, which makes defining functions with keyword-arguments easier.

Upvotes: 5

Arthur Ulfeldt
Arthur Ulfeldt

Reputation: 91534

this is strictly speaking not the most efficient way of writing this but it's clear

(defn myfunction [value & flags] 
  (cond (contains? (set flags) :a) 1
        (contains? (set flags) :b) 2)

it could be more efficient to factor (set flags) up.

Upvotes: 0

achim
achim

Reputation: 146

Lists (as well as vectors and maps) are not a good choice of data structure for value-based lookups (will be linear time), that's why clojure.core doesn't have such functions.

Sets do provide fast value-based lookups via "contains?", so how about

(defn foo [value & flags]
  (let [flags (set flags)]
    (if (contains? flags :add-one)
      (inc value)
      value)))

If there won't be more than one flag, you can use destructuring like this:

(defn foo [value & [flag]] …)

Upvotes: 9

Related Questions