Reputation: 141
(defmacro block [ctx & expr]
`(let [~@(mapcat (fn [[k v]] [k `~v]) ctx)]
~@expr
))
(defn action1 [] (print "action1") (rand-nth [true false]))
(defn action2 [] (print "action2") (rand-nth [true false]))
( block { __blockaddrabsolute "1_1" __blockaddr "1_1"}
( block {typeofparent "ummutate" __nodeid "c21f80" __blockaddr "1_1_1"} ( action1 ))
( block {__blockaddrabsolute "1_1_2" __nodeid "c60590" __blockaddr "1_1_2"} ( action2 ))
( block {__blockaddrabsolute "1_1_3" __nodeid "c60595" __blockaddr "1_1_3"} ( action1 ))
( block {__blockaddrabsolute "1_1_4" __nodeid "c60596" __blockaddr "1_1_4"} ( action2 ))
"end" )
I want to break the execution from macro evaluation if any of the action returns false.
Expected output :
action1 true
action2 true
action1 false
Upvotes: 1
Views: 133
Reputation: 16194
The short-circuiting behavior you want is available through if
/when
forms, so we can use macros to transform a series of forms in the body into nested when
forms:
(defmacro block [bindings & body]
(let [whens (reduce (fn [acc elem]
`(when ~elem ~acc))
(last body)
(reverse (butlast body)))]
`(let [~@(mapcat (fn [[k v]] [k `~v]) bindings)]
~whens)))
Then if we macroexpand
your sample block
form we get this (reformatted for readability):
(let* [__blockaddrabsolute "1_1" __blockaddr "1_1"]
(when (block {typeofparent "ummutate", __nodeid "c21f80", __blockaddr "1_1_1"} (action1))
(when (block {__blockaddrabsolute "1_1_2", __nodeid "c60590", __blockaddr "1_1_2"} (action2))
(when (block {__blockaddrabsolute "1_1_3", __nodeid "c60595", __blockaddr "1_1_3"} (action1))
(when (block {__blockaddrabsolute "1_1_4", __nodeid "c60596", __blockaddr "1_1_4"} (action2))
"end")))))
Because your action1
/action2
functions return random booleans you'll get varying results, but you do get the desired short-circuiting behavior. If any of the nested forms fail the when
test, the ultimate result will be nil.
I'd consider refactoring this by introducing a more focused, generally useful do
-like macro that short-circuits when any of its inner forms aren't truthy, and isn't concerned with bindings at all. Then use let
for your inner bindings:
(defmacro do-when [x & xs]
(if xs
`(when ~x (do-when ~@xs))
`~x))
(do-when
(let [x 1 y 2] (println "step 1") (= x (dec y)))
(rand-nth [true false])
"randomly successful result")
Upvotes: 2