Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Better way to nest if-let in clojure

Say I have a map of this form:

(def m {:a "A" :b "B"})

and I want to do something if :a and :b are both not nil, I can do:

(if-let [a (:a m)]
  (if-let [b (:b m)]
      ... etc ))

or

(if (and (:a m) (:b m))
  (let [{a :a b :b} m]
      ... etc ))

or even

(if (every? m [:a :b])
  (let [{a :a b :b} m]
      ... etc ))

Is there a neater (ie one-line) way to achieve this?

like image 569
Matthew Gilliard Avatar asked Jul 26 '12 16:07

Matthew Gilliard


3 Answers

I think a macro may be necessary here to create the behavior you want. I have never written one (yet) but the following representation suggests to me that this might be fairly straightforward:

(let [{:keys [a b]} m]
   (when (every? identity [a b])
      (println (str "Processing " a " and " b))))

Using the :keys form of destructuring binding and every? enables a single specification of a vector of keys to destructure and check, and the bound locals are available in a following code block.

This could be used to make a macro such as (when-every? [keys coll] code-with-bindings)

I may update this answer with the macro code if I can take the time to work out how to do it.

like image 88
Alex Stoddard Avatar answered Oct 09 '22 15:10

Alex Stoddard


You could use map destructuring -- a useful feature of Clojure. This also exploits the facts that and is short-circuiting, and any key in the first map not found in the second map gets nil, a falsy value:

(let [{a :a b :b} {:a 1 :b "blah"}] 
  (and a b (op a b)))

Okay, so it's two lines instead of one .... also this doesn't distinguish between nil and other falsy values.

like image 6
Matt Fenwick Avatar answered Oct 09 '22 14:10

Matt Fenwick


not-any? is a nice shortcut for this:

user> (not-any? nil? [(m :a) (m :b)])
true
user> (not-any? nil? [(m :a) (m :b) (m :d)])
false
user> 
like image 1
Arthur Ulfeldt Avatar answered Oct 09 '22 13:10

Arthur Ulfeldt