Fork me on GitHub
#meander
<
2024-03-06
>
hlship22:03:56

I'm pretty new to Meander, but I have some pretty straight-forward use cases; I'm having trouble understanding how to express them. Basic example; for this input:

{:widget/id           "xyz"
 :widget/manufacturer {:company/name "Widget Co."}
 :widget/parts        [{:part/id       123
                        :part/location {:bin/id 23}}
                       {:part/id       456
                        :part/location {:bin/id 97}}]}
I'd like to fold/spindle/mutilate to:
{:widget-id         "xyz"
 :manufacturer-name "Widget Co."
 :parts             [{:id  123
                      :bin 23}
                     {:id  456
                      :bin 97}]}
I'm at a loss for how to handle transforming the :widget/parts key. And I'd prefer a solution where I can adapt it to a case where I'm converting multiple widgets, not just one.

markaddleman23:03:37

Here’s a solution

markaddleman23:03:31

This is a slightly better one

markaddleman23:03:33

To explain some of the non-obvious pieces of the solution: • m/some is used to ensure that the key exists in the map. Without it, meander might match the wrong map. • Handling the vector of widget parts is a bit magicky. First, we capture each element of the vector into the memory variable !widget-part . During expansion, we try rewriting each element of the vector against the matching rules. The first match rule fails (because of the m/some keys don’t match). The second rule matches and returns the rewritten data.

hlship23:03:23

Is there something like cata, but I specify the rules right there? That would eliminate the m/some calls if I'm following you.

hlship23:03:26

i.e., can I use m/rewrite inline?

markaddleman23:03:38

Kind of. You can create a separate function which calls m/rewrite

markaddleman23:03:43

One sec and I’ll show you

hlship23:03:24

Would you consider what I'm doing a common use case? I think anyone getting Datomic data out on the wire would 😉

markaddleman23:03:54

Here’s an example of “inline” rewrite

markaddleman23:03:36

The m/app is necessary because the rewrite macro assumes it’s own micro-language on the right-hand side of the rewrite rule.

markaddleman23:03:50

In this case, it would be more straightforward to write this code using m/find

markaddleman23:03:05

It’s just that I normally reach for m/rewrite because it’s so compact and powerful.

markaddleman23:03:17

As for common use case: Yes! I use meander like this all the time.

markaddleman23:03:10

As an advanced use case, I will occasionally dynamically generate rewrite rules using the meander strategy namespace.

markaddleman23:03:39

Here’s an example of using m/find instead of m/rewrite

markaddleman23:03:07

(oh, you’re right, btw, in these last couple of examples the m/some are not necessary)

hlship23:03:44

I'm happy with this:

(def widget {:widget/id           "xyz"
             :widget/manufacturer {:company/name "Widget Co."}
             :widget/parts        [{:part/id       123
                                    :part/location {:bin/id 23}}
                                   {:part/id       456
                                    :part/location {:bin/id 97}}]})

(defn- rewrite-widget-part [part]
  (m/match part
           {:part/id       ?part-id
            :part/location {:bin/id ?bin-id}}
           {:id  ?part-id
            :bin ?bin-id}))

(m/rewrite widget
           {:widget/id           ?id
            :widget/manufacturer {:company/name ?company-name}
            :widget/parts        [(m/app rewrite-widget-part !widget-parts) ...]}
           {:widget-id         ?id
            :manufacturer-name ?company-name
            :parts             [!widget-parts ...]})
But it feels like there could be some syntax sugar instead of the m/app call.

hlship23:03:02

Thanks for the help!

hlship23:03:02

Strawman:

(m/rewrite widget
           {:widget/id           ?id
            :widget/manufacturer {:company/name ?company-name}
            :widget/parts        [(m/each !widget-parts
                                          {:part/id       ?part-id
                                           :part/location {:bin/id ?bin-id}}
                                          {:id  ?part-id
                                           :bin ?bin-id}) ...]}
           {:widget-id         ?id
            :manufacturer-name ?company-name
            :parts             [!widget-parts ...]})

hlship22:03:21

I don't have. a feel for how you combine the pieces.