Wouldn't it be nice if if-let allowed more bindings? Try this, which I hereby dedicate into the public domain so that anyone may use it freely in their code without restrictions:
(defn if-and-let* [bindings then-clause else-clause deshadower] (if (empty? bindings) then-clause `(if-let ~(vec (take 2 bindings)) ~(if-and-let* (drop 2 bindings) then-clause else-clause deshadower) (let ~(vec (apply concat deshadower)) ~else-clause)))) (defmacro if-and-let "Like if-let, but with multiple bindings allowed. If all of the expressions in the bindings evaluate truthy, the then-clause is executed with all of the bindings in effect. If any of the expressions evaluates falsey, evaluation of the remaining binding exprs is not done, and the else-clause is executed with none of the bindings in effect. If else-clause is omitted, evaluates to nil if any of the binding expressions evaluates falsey. As with normal let bindings, each binding is available in the subsequent bindings. (if-and-let [a (get my-map :thing) b (do-thing-with a)] ...) is legal, and will not throw a null pointer exception if my-map lacks a :thing key and (do-thing-with nil) would throw an NPE. If there's something you want to be part of the then-clause's condition, but whose value you don't care about, including a binding of it to _ is more compact than nesting yet another if inside the then-clause." ([bindings then-clause] `(if-and-let ~bindings ~then-clause nil)) ([bindings then-clause else-clause] (let [shadowed-syms (filter #(or ((or &env {}) %) (resolve %)) (filter symbol? (tree-seq coll? seq (take-nth 2 bindings)))) deshadower (zipmap shadowed-syms (repeatedly gensym))] `(let ~(vec (apply concat (map (fn [[k v]] [v k]) deshadower))) ~(if-and-let* bindings then-clause else-clause deshadower))))) => (if-and-let [x (:a {:b 42}) y (first [(/ x 3)])] [x y] :nothing) :nothing => (if-and-let [x (:a {:a 42}) y (first [(/ x 3)])] [x y] :nothing) [42 14] => (if-and-let [x (:a {:a 42}) y (first [])] [x y] :nothing) :nothing Note that this is not quite as simple as the obvious naive implementation: (defmacro naive-if-and-let ([bindings then-clause] `(naive-if-and-let ~bindings ~then-clause nil)) ([bindings then-clause else-clause] (if (empty? bindings) then-clause `(if-let ~(vec (take 2 bindings)) (naive-if-and-let ~(vec (drop 2 bindings)) ~then-clause ~else-clause) ~else-clause)))) but what happens if a name used in the if-and-let is already bound in the enclosing context is instructive: => (let [x 6] (if-and-let [x (:a {:b 42}) y (first [(/ x 3)])] [x y] x)) 6 => (let [x 6] (if-and-let [x (:a {:a 42}) y (first [])] [x y] x)) 6 => (let [x 6] (naive-if-and-let [x (:a {:b 42}) y (first [(/ x 3)])] [x y] x)) 6 => (let [x 6] (naive-if-and-let [x (:a {:a 42}) y (first [])] [x y] x)) 42 As you can see, the x in the else clause in naive-if-and-let sometimes sees the x binding in the if-and-let (if that succeeded) and sometimes sees the enclosing binding (if not), when it should always refer to the enclosing (let [x 6] ...). The non-naive if-and-let discovers all local bindings that might be shadowed by walking the left hand sides of the new bindings and tree-walking the data structure there to extract symbols, which it filters further against &env. It outputs an enclosing let that saves all of these to non-shadowed locals named with gensyms, and wraps every else clause emission in a let that restores the original bindings of these symbols from these gensym locals. The tree-walking makes it work even with destructuring its the binding vector: => (let [x 6] (if-and-let [{x :a} {:a 42} y (first [(/ x 3)])] [x y] x)) [42 14] => (let [x 6] (if-and-let [{x :a} {:a 42} y (first [])] [x y] x)) 6 It also unshadows defs: => (def x 6) => (if-and-let [{x :a} {:a 42} y (first [])] [x y] x) 6 That's from the (or ... (resolve %)) part of the outer filter on the walked tree. Remove that and leave the outer filter as just (filter (or &env {}) ...), and that last test produces 42 instead. Not that you should really be shadowing defs with locals anyway. That's always prone to cause problems. Not bad for only 18 lines of actual code, hmm? -- You received this message because you are subscribed to the Google Groups "Clojure" group. To post to this group, send email to clojure@googlegroups.com Note that posts from new members are moderated - please be patient with your first post. To unsubscribe from this group, send email to clojure+unsubscr...@googlegroups.com For more options, visit this group at http://groups.google.com/group/clojure?hl=en --- You received this message because you are subscribed to the Google Groups "Clojure" group. To unsubscribe from this group and stop receiving emails from it, send an email to clojure+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.