You've discovered the error (or Either) monad!

https://brehaut.net/blog/2011/error_monads

Michael Blume <mailto:blume.m...@gmail.com>
November 26, 2014 at 8:06 PM
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))))


--
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 <mailto:clojure+unsubscr...@googlegroups.com>.
For more options, visit https://groups.google.com/d/optout.
Fluid Dynamics <mailto:a2093...@trbvm.com>
November 26, 2014 at 5:10 AM
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 <mailto:clojure+unsubscr...@googlegroups.com>.
For more options, visit https://groups.google.com/d/optout.

--
Sam Ritchie (@sritchie)
Paddleguru Co-Founder
703.863.8561
www.paddleguru.com <http://www.paddleguru.com/>
Twitter <http://twitter.com/paddleguru>// Facebook <http://facebook.com/paddleguru>

--
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