Search code examples
clojuremacros

Clojure macro to allow writing code without let bindings


I am trying to write a macro that will allow me to do the following

(without-nesting
  (:= x 1)
  (:= y 2)
  (:= z 3)
  (db-call x y z)
  (:= p 33)
  (db-call x y z p))

becomes

(let [x 1 
      y 2 
      z 3]
  (db-call x y z)
  (let [p 33]
    (db-call x y z p)))  

So far my implementation has been the following

(defn assignment?
  [[s _]]
  (= s ':=))

(defmacro without-nesting
  [& body]
  (let [[bindings xs] (split-with assignment? body)
        [non-bindings remaining] (split-with (complement assignment?) xs)]
    `(let [~@(mapcat rest bindings)]
       ~@non-bindings
       ~(when (seq remaining)
          `(without-nesting ~@remaining)))))

I'm having issues when remaining is going to be empty. In my current implementation a nil gets placed which prevents the last form in non-bindings to return its value. I have no clue on how to proceed with a recursive macro. Can someone help

UPDATE:

So I was able to get rid of the nil but I just want to know if there's a better way to deal with this

(defmacro without-nesting
  [& body]
  (let [[bindings xs] (split-with assignment? body)
        [non-bindings remaining] (split-with (complement assignment?) xs)]
    `(let [~@(mapcat rest bindings)]
       ~@non-bindings
       ~@(if (seq remaining)
           [`(without-nesting ~@remaining)]
           []))))  

Also would this be a good way to write code? Or do you see any caveats? For me it looks more linear as compared to nested let

I do see how ppl may abuse this. In the case of let, if the nesting becomes too much, then it's a hint to refactor the code. This might hide that


Solution

  • Just use let. It is already recursive. To incorporate function calls where you only care about the side effects, the convention is to bind to an underscore.

    (let [x 1 
          y 2 
          z 3
          _ (db-call x y z)
          p 33
          _ (db-call x y z p)])