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))
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]] …)
clojure.contrib.def
includes the defnk
-macro, which makes defining functions with keyword-arguments easier.
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)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With