Fork me on GitHub
#meander
<
2020-09-06
>
dregre16:09:52

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?

dregre16:09:00

Total noob to Meander here.

dregre16:09:21

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}]}

jimmy16:09:14

Hey @UAYUERYQ5 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

dregre18:09:04

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

🙂 1
jimmy20:09:41

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.

dregre22:09:59

Ah, that’s not too bad!

dregre22:09:50

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.

jimmy22:09:31

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

jimmy22:09:05

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

jimmy22:09:31

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.

dregre01:09:56

Unfortunately, nothing like that. In specter terms, the namespace is determined by the path. Here's a real-ish example. Input:

dregre01:09:03

{:name "Joe"

 :addresses
 [{:street-name "Test St."
   :city "Olympia"
   :state "NJ"}]

 :identifications
 [{:type :license
   :issuer
   {:name "DMV"
    :entries
    [{:code "DLSDLKDSK"
      :algo "akkak"}]}}]}

dregre01:09:19

{:person/name "Joe"
 
 :person/addresses
 [{:address/street-name "Test St."
   :address/city "Olympia"
   :address/state "NJ"}]

 :person/identifications
 [{:identification/type :license
   :identification/issuer
   {:issuer/name "DMV"
    :issuer/entries
    [{:entry/code "DLSDLKDSK"
      :entry/algo "akkak"}]}}]}

dregre02:09:58

And this is how I solved it in specter (but it's horrendous — if I keep the specter I'll break this apart a bit):

jimmy16:09:03

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

dregre19:09:49

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!

dregre16:09:06

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