hi, I have a question related to in place transformation, I'll try to explain it with an example. I have this data:
{:records [{:name "A" :value 1 :foo "not-important"}
{:name "B" :value 2 :bar "not-important"}]}
and I want this output:
{:records [{:name "A" :value 0}
{:name "B" :value 2}]}
for the list of records, I want to keep only some keys, :name
and :value
, and if :name
value is A
I want to set the value to 0
. Right now, I'm doing it in 2 steps, the first one like this:
(m/rewrite records
{:records [{:name !name :value !value} ...]}
{:records [{:name !name :value !value} ...]})
and then I use specter to update some of the records.
Is it possible to do both transformations currently with meander, or will be better to wait for the next release?you probably need a record-transforming function, and then use m/app
to call it in the RHS of the rewrite rule
`(m/rewrite data {:records [!r ...]} {:records [(m/app transform-record !r) ...]})`
or maybe abuse m/cata to match on both the overall shape and each record with the same rule
@jlle Here is one way to do that in meander directly. Inline:
(m/rewrite {:records [{:name "A" :value 1 :foo "not-important"}
{:name "B" :value 2 :bar "not-important"}]}
{:records [(m/or (m/let [!value 0] {:name (m/and "A" !name)})
{:name !name :value !value}) ...]}
{:records [{:name !name :value !value} ...]})
Extracting out into a with.
(m/rewrite {:records [{:name "A" :value 1 :foo "not-important"}
{:name "B" :value 2 :bar "not-important"}]}
(m/with [%record (m/or (m/let [!value 0]
{:name (m/and "A" !name)})
{:name !name :value !value})]
{:records [%record ...]})
{:records [{:name !name :value !value} ...]})
These two are equivalent. Just sometimes if things get complicated, turning them into a with can help. There are a few things I am doing one. First I am using m/or
to match on two different patterns. Then I am using m/let
to say that if this pattern matches at 0
to !value
. Then I am using m/and
to say that :name
should be “A” and to add it to !name
. If you find yourself doing this a lot, you can also look into defsyntax to make something more succinct. But I would start here.And since @qythium mentioned cata. Here is the cata way of doing it
(m/rewrite {:records [{:name "A" :value 1 :foo "not-important"}
{:name "B" :value 2 :bar "not-important"}]}
{:records [(m/cata !record) ...]}
{:records [!record ...]}
{:name (m/and "A" ?name)} {:name ?name :value 0}
{:name ?name :value ?value} {:name ?name :value ?value})
I don’t think I’d call this an abuse. Actually reads fairly well. The biggest thing to watch out for is order. {:name ?name :value ?value}
will match any map, even if it doesn’t have :name
as a key (it will assign nil
to ?name`) . So it needs to be at the end, or constrained with a m/some
.
heh, I called it an "abuse" because the structure didn't seem like it was recursive in the same way as eg. syntax trees or hiccup vectors that called for a catamorphism
Yeah that is true. The cata will continue to recurse if there is another :records entry.
using with
in that above code to extract a sub-pattern is a nice motivating example for the syntax though, I've personally been scared off by "yet another %prefix" to really look into it
Finally (and yes I know this is information overload), there is really no reason you can’t just pull things out into a function.
(defn transform-record [record]
(m/rewrite record
{:name (m/and "A" ?name)} {:name ?name :value 0}
{:name ?name :value ?value} {:name ?name :value ?value}))
(m/rewrite {:records [{:name "A" :value 1 :foo "not-important"}
{:name "B" :value 2 :bar "not-important"}]}
{:records [(m/app transform-record !record) ...]}
{:records [!record ...]})
btw, cata is not documented yet, isn't it? or at least I wasn't able to find any docs
There is some simple doc string documentation in the cljdoc. But not enough to explain it. I should maybe write an article on it. I'm just beginning to understand it and it fairly need and not at all obvious.
Glad the examples helped.