meander

All things about https://github.com/noprompt/meander Need help and no one responded? Feel free to ping @U5K8NTHEZ
dregre 2020-09-06T16:28:52.219400Z

Hello Meanderites! Been banging my head against the wall for this one; let's say I wanted to replace all the keys' namespaces in a map with a given namespace, how could I go about doing that?

dregre 2020-09-06T16:29:00.219700Z

Total noob to Meander here.

dregre 2020-09-06T16:40:21.222900Z

Here's a contrived example. Say I have data shaped like this more or less:

{:foo 1
 :boo [{:abc true}]}
I'd like the root keys to be namespaced bar and the keys in the :boo maps to be namespaced far , so:
{:bar/foo 1
 :bar/boo [{:far/abc true}]}

jimmy 2020-09-07T16:14:03.238800Z

Here is the example directly.

(m/rewrite example
    

  {:addresses [(m/map-of (m/keyword !address-k) !address-v) ..!addresses]
   :identifications [{:issuer 
                      {:entries [(m/map-of (m/keyword !entry-k) !entry-v) ..!entries]
                       & (m/map-of (m/keyword !issuer-k) !issuer-v)} 
                      & (m/map-of (m/keyword !id-k) !id-v)}
                     ..!ids]
   & (m/map-of (m/keyword !person-v) !person-k)}


  {:person/addresses [(m/map-of (m/keyword "address" !address-k) !address-v) ..!addresses]
   :person/identifications [{:identification/issuer 
                             {:issuer/entries [(m/map-of (m/keyword "entry" !entry-k) !entry-v) ..!entries]
                              & (m/map-of (m/keyword "issuer" !issuer-k) !issuer-v)} 
                             & (m/map-of (m/keyword "identification" !id-k) !id-v)}
                            ..!ids]
   & (m/map-of (m/keyword "person" !person-v) !person-k)})
Here it is with some bits factored out.
(m/rewrite example
  [?namespace (m/map-of (m/keyword !k) !v)]
  (m/map-of (m/keyword ?namespace !k) !v)


  {:addresses [!addresses ...]
   :identifications [{:issuer 
                      {:entries [!entry ..!entries]
                       & !issuer} 
                      & !id} ..!ids]
   & ?person}

  {:person/addresses [(m/cata ["address" !addresses]) ...]
   :person/identifications [{:identification/issuer 
                             {:issuer/entries [(m/cata ["entry" !entry]) ..!entries]
                              & (m/cata ["issuer" !issuer])}
                             & (m/cata ["identification" !id])}
                            ..!ids]
   & (m/cata ["person" ?person])})
You could probably use defsyntax to get an even cleaner representation

dregre 2020-09-07T19:22:49.241100Z

Ah, very cool, much obliged! Sorry for all the work! Feel free to use this if you think it merits being added as an example to the docs!

dregre 2020-09-06T16:41:06.223800Z

And I'd like to have arbitrarily many keys at each level.

jimmy 2020-09-06T16:58:14.228100Z

Hey @andre206 welcome 🙂 While there are definitely ways to solve your problem using meander, this is definitely not the sort of problem meander is focused on. Meander is focused more on concrete transformations and less on generic ones, that is one way it differs from things like specter. That said, by leveraging clojure’s facilities for dealing with arbitrarily nested things, we can definitely tackle this problem. First we can start with the non-nested case

(m/rewrite {:foo 1 :boo 3}
  (m/map-of (m/keyword !ks) !vs)
  (m/map-of (m/keyword "bar" !ks) !vs))
Then we can use clojure.walk with this solution to make it work on arbitrary nested levels.
(require '[clojure.walk :as walk])

(walk/postwalk 
 (fn [x]
   (m/rewrite x
     (m/map-of (m/keyword !ks) !vs)
     (m/map-of (m/keyword "bar" !ks) !vs)
     
     ;; catch all for all non-map values
     ?x ?x))
 {:foo 1
  :boo [{:abc true}]})
Of course, this could have been accomplished without meander in not too different of a way. But hopefully that helps

dregre 2020-09-06T18:25:04.228300Z

Ah, many thanks! I think I understand Meander's purpose a bit more now

🙂 1
jimmy 2020-09-06T20:48:41.228600Z

Glad that helped. I definitely should have mentioned the pure meander solution though.

(require '[meander.strategy.epsilon :as r])

((r/top-down
  (r/rewrite 
   (m/map-of (m/keyword !ks) !vs)
   (m/map-of (m/keyword "bar" !ks) !vs)
   ?x ?x))

 {:foo 1
  :boo [{:abc true}]})
Strategies definitely let you achieve a lot of things for arbitrarily nested collections. But top down is like clojure.walk in many ways. In general though, I think meander shines most when you have particular transformations you want to do, rather than generic ones.

dregre 2020-09-06T22:30:59.229400Z

Ah, that’s not too bad!

dregre 2020-09-06T22:35:50.235200Z

My problem, you see, is only partially generic. I’m trying to specify namespaces for nested entities (the namespace at each level of nesting is specific). I tried specter first then came upon Meander on HN. Specter’s API felt a bit clunky, and Meander’s logic style programming immediately appealed to me — but I realize my problem is not a perfect fit.

jimmy 2020-09-06T22:49:31.235500Z

How do you know at what level of nesting you should use what namespace? Is there any predictability to the structure?

jimmy 2020-09-06T22:50:05.235700Z

If you give your full real problem, there might be an good answer. Or at least I would know if there wasn’t.

jimmy 2020-09-06T22:55:31.235900Z

Like for example, if there is some :type key that tells you the namespace and it was all just maps you could do something like this:

(def example 
  {:type "thing"
   :stuff {:type "stuff"
           :a 3
           :foo {:type "foo"
                 :c 5}}
   :b 3})

(m/rewrite example
  {:type ?type
   & (m/map-of (m/keyword !ks) !vs)}
  {:type ?type
   & (m/map-of (m/keyword ?type !ks) (m/cata !vs))}

  ?x ?x)
I could probably figure out a way to make it a bit more flexible than just maps, would probably just take some thinking.