Fork me on GitHub
#meander
<
2023-10-07
>
Coby Tamayo05:10:02

Is it possible to match the path to a node matching some predicate, through an arbitrarily nested data structure? For example, I can do this to get the binding I care about:

(def pull
    [:db/id
     {:menu/items
      [:db/id
       {:menu.item/entities
        [{:translatable/fields
          [:field/key
           :field/content]}]}]}])

  (m/search pull
            (m/$ (m/scan (m/pred translatable-binding? ?binding)))
            ?binding)
  ;; => (#:translatable{:fields [:field/key :field/content]})
But what I'd really like to be able to do is something like this:
(m/search pull
            (PATH-OP (m/scan (m/pred translatable-binding? ?path)))
            ?path)
  ;; => [1 :menu/items 1 :menu.item/entities 0]

Coby Tamayo08:10:05

I've had some luck with this simplified example using map? as a predicate:

(m/search [:a :b :c {:d [:e {:f :g}]} [{}]]
            [(m/not (m/pred map?)) ..?n {?k ?v} & _]
            (concat [?n ?k] (m/cata (doto ?v prn))))
  ; (out) [:e {:f :g}]
  ;; => ((3 :d))
However, I'm not sure why the recursion is coming back with nil. The value of ?v is what I expect based on the output.

Coby Tamayo05:10:38

ah, I was using cata on the RHS when it should've been on the left.

Coby Tamayo05:10:14

Got this working!

(let [tree pull
        search-key :translatable/fields
        pred #(some #{'* :field/content} %)]
    (m/search
      tree

      {~search-key (m/pred pred ?v)}
      {search-key ?v}

      [_ ..?n (m/cata ?map) & _]
      [[?n] ?map]

      [_ ..?n {(m/and (m/not ~search-key) ?k) (m/cata ?v)} & _]
      (let [[path m] ?v]
        [(vec (concat [?n ?k] path)) m])))

Coby Tamayo05:10:19

^ That replaces about 75 lines of baroque, buggy Clojure...I think I'm in love 😍