Instead of the deshadowing logic, why not (defn if-and-let* [bindings then-clause else-fn-name] (if (empty? bindings) then-clause `(if-let ~(vec (take 2 bindings)) ~(if-and-let* (drop 2 bindings) then-clause else-fn-name) (~else-fn-name))))
(defmacro if-and-let [bindings then-clause else-clause] (let [efname (gensym)] `(let [~efname (fn [] ~else-clause)] ~(if-and-let* bindings then-clause efname)))) On Wed Nov 26 2014 at 4:11:00 AM Fluid Dynamics <a2093...@trbvm.com> wrote: > 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. > -- 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.