Fork me on GitHub
#clojure
<
2022-09-29
>
eskos07:09:34

Bit of a thought experiment, what’d be the most up to date way to compare the structure of two arbitrary functions? I was going through my old personal projects recently, and one of them was a function differ; given (as data) (defn ^:private x [a] a) and (defn x ([a] a) ([a b] a)) it would compare the various differences and produce a report of added/removed params and arities, visibility modifications etc. Now, since this code is at this point ancient and one of my very first Clojure toys, it’s fallen into the pit of unmaintainable arcane mystery and I was thinking that maybe there’s actually something like this already out there and if not, there has to be better ways to do this than the superhacky “beat it until it works” approach my original project uses… 🙂

p-himik07:09:15

There are some libraries out there for structural diff'ing of Clojure data structures. Not sure whether they'd handle metadata and other reader tags.

reefersleep08:09:12

Maybe one of those more general libs could be used as a base for some the function-specific diffing that you’ve built?

eskos09:09:37

Possibly yes, the less I’d need to do by hand the better, unless of course absolutely needed. What would be the names of those libs? 🙂

Ed09:09:27

Ray and the #repl-acement’s have been looking at using spec to conform clojure code into a canonical representation that can be stored, queried and diffed and so on. Maybe that approach would be easier to deal with rather than the literal sexeps?

flowthing10:09:42

Depending on what you mean by structure and what “etc.” entails, you might be able to glean everything you need by just inspecting the runtime. Something like:

(transduce
  (comp
    (map ns-interns)
    (mapcat vals)
    (map meta)
    (map #(select-keys % [:ns :name :private :arglists])) ; whatever meta you need
    )
  (completing
    (fn [qualified-symbol->meta {:keys [ns name] :as meta}]
      (assoc qualified-symbol->meta (symbol (str ns) (str name)) (dissoc meta :ns :name))))
  (sorted-map)
  ['my.ns1 'my.ns2] ; or filter your namespaces from (all-ns)
  )

flowthing10:09:34

Could just run that on the old version, spit the result into an EDN file, then run it on the new version, slurp, and throw clojure.data/diff or something at it.

kwladyka11:09:47

(defn subcoll? [sub coll]
  (let [diff (clj-data/diff sub coll)]
    (nil? (first diff))))
I believe diff will to the job. Above is an axample.
(testing "spec"
    (is (test-utils/subcoll? {:response {:status 200}}
                             (-> (gen/generate (s/gen ::spec-labels/labels-orders))
                                 (assoc :data-structure "atomstore/labels")
                                 (assoc-in [:api-client :code] "kh")
                                 (request-generate-labels)))
        "random request"))
Just check if {:response {:status 200}} is in the response map. But can be also:
(is (test-utils/subcoll?
          {:response {:status 400,
                      :body {:logs {:cause {:pdf-template "examples/foo"}
                                    :type "error"
                                    :code "pdf-template"
                                    :message "One of label-template doesn't exist or you don't have access."}}}}
          (request-generate-labels
            {:data-structure "atomstore/labels"
             :api-client {:code "kh"}
             :orders [{:code "1"
                       :lang "pl"
                       :products [{:pdf-template "examples/foo"
                                   :name "Nazwa Produktu"
                                   :ingredients "pszenica kamut, cukier, żyto, sól, orzeszki ziemne"
                                   :countries-of-origin "Polska"
                                   :expiry-days 365
                                   :unit "szt."
                                   :quantity 1
                                   :custom {:ingredients "pszenica kamut, cukier, żyto, sól, orzeszki ziemne"
                                            :countries-of-origin "Poland"
                                            :expiry-days 365}}]}]}))

kwladyka11:09:20

But you can use diff in different way, than in this examples

kwladyka11:09:28

to extract differencnes

eskos15:09:45

I probably should've been a bit more clear: I don't really need structural diff but semantic diff or at least something which cuts the input sexprs into its components; meta partially works for what I'm trying to achieve, but it's not the full picture either. #repl-acement seems to be on similar track at least partially, so there's that.

onionpancakes18:09:46

I would look into conforming the function forms using clojure.spec, and comparing the conformed values.

Drew Verlee21:09:45

Is there a way to concat the results of clojure.core/iteration resulting iteration? the way i have it setup right now it's returning a vector of vectors that have the results as where i would perfer just a vector of results. it says (vf ret) will be included in the results which doesn't give me control over the final collection right?

ghadi21:09:54

cat transducer or apply concat to stitch results together

Drew Verlee21:09:33

gotcha, yea that makes sense

Darin Douglass22:09:09

I find I almost always (sequence cat (iteration …))

Drew Verlee22:09:12

Thanks @U02EA2T7FEH i need to internalize how sequence and cat work together.