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.