Fork me on GitHub

I wrote a cute little macro that does a threading diff: 🧵


    (assoc :a 1 :b {})
    (assoc-in [:b :c] [1 2 3 4])
    (update-in [:b :c 2] inc)
    (update-in [:b :c] reverse))

if you run what’s in the comment block, it’ll print out:


(ns playground.thread-diff
  (:require [clojure.pprint :as pprint]
            [lambdaisland.deep-diff2 :as ddiff]
            [clojure.walk :as walk]
            [clojure.string :as str]))

(defn print-diff! [a b]
  (ddiff/pretty-print (ddiff/diff a b) (ddiff/printer {:width 80})))

(defn outerpose [x ys]
  (concat [x] (interpose x ys) [x]))

(defmacro -diff->
  "Threads the expr through the forms. Inserts x as the
  second item in the first form, making a list of it if it is not a
  list already. If there are more forms, inserts the first form as the
  second item in second form, etc."
  {:added "1.0"}
  [in-x & in-forms]
  (loop [x [::nothing in-x],
         forms (concat
                 ;; Starting:
                 [(list (list 'fn '[[old new]]
                              (list 'println "Starting -diff-> with:" '(pr-str new) "\n")
                              '[old new]))]
                   ;; run with old and new context steps
                     (fn [form]
                       (list (list 'fn '[[_ new]] ['new (list '-> 'new form)])))
                   ;; report diff result steps
                   (mapv (fn [form]
                           (list (list 'fn '[[old new]]
                                       (list 'println "")
                                       ;; print Running step
                                       (list 'println "Running: " (list 'str "(-> "  (list pr-str 'old) " " (pr-str form) ")"))
                                       ;; print diff
                                       '(print-diff! old new)
                                       '[old new])))
    (if forms
      (let [form (first forms)
            threaded (if (seq? form)
                       (with-meta `(~(first form) ~x [email protected](next form)) (meta form))
                       (list form x))]
        (recur threaded (next forms)))
      (list last x))))


    (assoc :a 1 :b {})
    (assoc-in [:b :c] [1 2 3 4])
    (update-in [:b :c 2] inc)
    (update-in [:b :c] reverse))



the way it works, is by wrapping the steps that do actions in functions that take 2 args: old and new.


the wrapping functions apply a step, put its result into new, and the input to the step into old


so if you had inc, it would basically turn into (fn [[old new]] [new (inc new)])


then there’s a function that takes the initial value and turns it into [::nothing in-x] at the beginning and calls last at the end with (list last x)

Noah Bogart19:08:35

that's so cool and fun!

🙏 2

Yeah, neato! Thanks for sharing!

Drew Verlee03:08:05

Super cool. I assume this will be useful in every day development? Would it work to replace the threading macro with this temporarily to get more info about some context? I can't see why not...


Are the clojure.pprint, clojure.string, and clojure.walk requires relicts from earlier experiments?


Must be. I see that those aren’t needed here. I think I was trying to write the macro a different way


Also, it would be great to have a version that plays nicely with large data structures, perhaps by printing just smaller portion of the diff.


Finally, I think you can make it more general by accepting the threading symbol as an argument. So it's automatically usable for both -> and ->>. As in

(tdiff -> ...)

💯 1