Fork me on GitHub

Wondering how I can do the following w/out a group-by:

(let [coll [{:foo "a" :bar "b" :baz 123}
            {:foo "x" :bar "y" :baz 666}
            {:foo "a" :bar "b" :baz 987}]]
  (m/search (group-by (juxt :foo :bar) coll)
    (m/scan [[?foo ?bar] [{:baz !baz} ...]])
    {:foo ?foo
     :bar ?bar
     :children (for [v !baz]
                 {:baz v})}))

;; =>

({:foo "a", :bar "b", :children ({:baz 123} {:baz 987})}
 {:foo "x", :bar "y", :children ({:baz 666})})
I figure it’d need some way of remembering the different foo & bar values and baz associated w/ them…keep in mind that the maps w/ same foo/`bar` pairs aren’t necessarily in order.


This comes up from time to time (group-by) which currently has no clean solution but will (hopefully) later this year. For these situations group-by is what I typically recommend because the alternative is not always that great.

(let [coll [{:foo "a" :bar "b" :baz 123}
            {:foo "x" :bar "y" :baz 666}
            {:foo "a" :bar "b" :baz 987}]]
  (m/rewrite coll
    (m/with [%it {:foo ?foo, :bar ?bar, :baz !baz}]
      [%it . (m/or %it !not-it) ...])
    ({:foo ?foo, :bar ?bar, :children [!baz ...]}
     & (m/cata [!not-it ...]))

Here the issue is performance due to nodes being visited again and again. We can try a different approach.
(let [coll [{:foo "a" :bar "b" :baz 123}
            {:foo "x" :bar "y" :baz 666}
            {:foo "a" :bar "b" :baz 987}]]
  (m/rewrite [coll {}]
    [[{:foo ?foo, :bar ?bar, :baz ?baz} & ?rest]
     (m/and ?index
            (m/or {[?foo ?bar] ?children}
                  (m/let [?children []])))]
    (m/cata [?rest {[?foo ?bar] [?baz & ?children] & ?index}])

    [[] (m/map-of [!foo !bar] !children)]
    ({:foo !foo, :bar !bar, :children !children} ...)))
This approach is, eh, not terribly attractive but I think it should be more performant than the other.


Either way, I think group-by is much easier to wield and has more utility than what Meander can offer at the moment (which is okay!).

👍 2

Still, I think its a fun exercise to write out the semantically equivalent rules of a particular group by as practice.


Hey. This seems simple enough but it’s been hours (I don’t want to admit how many) and I haven’t got a solution. I want to turn this

 [:<> 'one ['three [:<> 'two]]]
 [:<> [:<> 'three]]]
into this
['four 'one ['three 'two] 'three]
I’m trying to splat any vector that starts with that keyword into the parent vector.


This comes up because I have multiple things on the rhs of my rewrite rule but I don’t want it in a vector in the parent vector. e.g.

{:node-type :module
       :body [(m/app f !body) ...]}
      [:<> (ns file-name-maybe)
       & [!body ...]]

      {:node-type :name
       :id (m/app keb-sym ?id)}
The best idea I’ve had so far is to denote the ones that I want to flatten and flatten them after.


Oh. This is interesting. I kept trying things that worked top down but then I’d skip collapsing a level. I needed something that worked botom up.


(defn splat-fragments
      (sequential? xs) (->> (map splat-fragments xs)
                             #(if (and (sequential? %) (= :<> (first %)))
                                (rest %)
                            (apply concat))
      :else xs))


(let [input [[:<>]
             [:<> 'one ['three [:<> 'two]]]
             [:<> [:<> 'three]]]]
  (m/rewrite input
    (m/with [%splice (m/or [:<> . (m/or %splice %node) ...] %node)
             %node (m/cata !node)]
      [%splice ...])
    [!node ...]

    ?x ?x))
;; =>
[four one [three two] three]


Somewhere on one of my hard drives I had a generic rule for this kind of thing.


You can do a similar move to flatten operations like + or something.


Thanks. I’m still digesting it.


This might be a tad clearer?

(m/rewrite input
  (m/with [%splice [:<> . %main ...]
           %node (m/cata !node)
           %main (m/or %splice %node)]
    [%main ...])
  [!node ...]

  ?x ?x)


m/with and m/cata are what you want to reach for these kinds of situations. Think grammar. Think parser.


I haven’t used ‘with’ successfully yet. This example helps me a lot though. Thanks!


m/with is conceptually like letfn if that helps at all.