Jai
Jai

Reputation: 141

How to break on a condition from recursion in clojure macro?

(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

Answers (1)

Taylor Wood
Taylor Wood

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

Related Questions