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 <[email protected]> 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 [email protected]
> Note that posts from new members are moderated - please be patient with
> your first post.
> To unsubscribe from this group, send email to
> [email protected]
> 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 [email protected].
> 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 [email protected]
Note that posts from new members are moderated - please be patient with your
first post.
To unsubscribe from this group, send email to
[email protected]
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 [email protected].
For more options, visit https://groups.google.com/d/optout.