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.

Reply via email to