Patrick Fant
Patrick Fant

Reputation: 55

Clojure: succinctly forward optional values

I've written a probability function in Clojure that takes an optional hash-map of options:

(defn roll-lte
  ([n d] (/ n d))
  ([n d options]
    (let [p (/ n d)
          roll-type (:type options :normal)]
      (cond
        (= roll-type :advantage) (- (* p 2) (* p p))
        (= roll-type :disadvantage) (* p p)
        (= roll-type :normal) p
        :else (throw (IllegalArgumentException. "Invalid roll type."))))))

This works as intended, but the idea is to write other functions that build off of this one -- for example:

(defn roll-gte
  ([n d] (roll-lte (- d n -1) d))
  ([n d options] (roll-lte (- d n -1) d options)))

The two arities in roll-lte make building off of the function awkward and repetitive, especially in cases like the above where options is simply being forwarded to roll-lte. Is there a more concise and less repetitive way to achieve this?

Upvotes: 2

Views: 82

Answers (1)

Taylor Wood
Taylor Wood

Reputation: 16194

When I have functions with multiple arities, I usually try to have the lower-arity versions call the higher-arity versions with safe default arguments. The "main" implementation of the function usually ends up being the highest-arity body:

(defn roll-lte
  ([n d] (roll-lte n d nil))
  ([n d {:keys [type]
         :or   {type :normal}}]
   (let [p (/ n d)]
     (case type ;; used case instead of cond here
       :advantage (- (* p 2) (* p p))
       :disadvantage (* p p)
       :normal p
       (throw (IllegalArgumentException. "Invalid roll type."))))))

I also used :or in the options map destructuring above to set the default value for type, which allows the lower-arity functions to just pass a nil options map.

(defn roll-gte
  ([n d] (roll-gte n d nil))
  ([n d options] (roll-lte (- d n -1) d options)))

(roll-gte 3 4) ;=> 1/2
(roll-gte 3 4 {:type :advantage}) ;=> 3/4

Upvotes: 3

Related Questions