meander

All things about https://github.com/noprompt/meander Need help and no one responded? Feel free to ping @U5K8NTHEZ
shaun-mahood 2019-11-15T17:22:53.214100Z

I'm trying to wrap my head around the basics of Meander, replicating things I use frequently in Clojure. I've managed to get my data into the shape [{:key1 [val1]} {:key1 [val2]} {:key2 [val3]} ...], and I want to group it so that it looks like [{:key [val1 val2]} {:key2 [val3]}...]. Not really sure where to look or if this even makes sense to try to do in Meander.

noprompt 2019-11-15T17:59:25.215Z

(defn rule* [x]
  (let [x* (m/rewrite x
             [[{?k ?v} & ?rest] #{{?k [& ?vs]} ^& ?rest-set}]
             [?rest #{{?k [?v & ?vs]} ^& ?rest-set}]

             [[{?k ?v} & ?rest] ?rest-set]
             [?rest #{{?k [?v]} ^& ?rest-set}]

             [[] ?rest-set]
             ?rest-set

             ?x
             ?x)]
    (if (= x x*)
      x
      (recur x*))))

(defn rule [x]
  (rule* [x #{}]))

(rule '[{:key1 [val1]} {:key1 [val2]} {:key2 [val3]}])
;; =>
#{{:key1 [[val2] [val1]]} {:key2 [[val3]]}}

shaun-mahood 2019-11-15T18:01:02.216Z

Awesome, thanks! I'm kind of glad it needed something like that, I thought I was missing something super basic 🙂

jimmy 2019-11-15T18:01:33.216400Z

Grouping is something we want to make easier.

noprompt 2019-11-15T18:02:43.218600Z

Cleaning it up…

snoe 2019-11-15T18:02:51.218800Z

It would be nice if it was more visually obvious what was going on there, I'm not sure the meander version here is better than plain old clj.

shaun-mahood 2019-11-15T18:02:53.218900Z

I've been haunted by group-by for a long time (I don't think I've ever wanted an end result that looked like what it gives you), so this is pretty exciting

1đź’Ż
noprompt 2019-11-15T18:05:06.219800Z

@snoe Yeah that’s fair. I’ve been pretty happy to recommend people use group-by.

jimmy 2019-11-15T18:05:57.222200Z

I've tossing around in my head something like memory variables but for maps instead of vectors. But haven't come up with anything concrete or good yet.

snoe 2019-11-15T18:05:58.222300Z

on the other hand if you guys do figure out some way to make that more clear, I would love to stop using group-by

jimmy 2019-11-15T18:07:27.224500Z

Or some way to tell a variable how it aggregates results. So memory variables are conj. But you might want a sum or an assoc. Or whatever.

noprompt 2019-11-15T18:12:27.225300Z

(defn rule* [x]
  (let [x* (m/rewrite x
             {:in [{?k [!vs ...] & ?rest-1} & ?tail]
              :out {?k [!vs ...] & ?rest-2}}
             {:in [?rest-1 & ?tail]
              :out {?k [!vs ...] & ?rest-2}}

             {:in [{?k [!vs ...] & ?rest-1} & ?tail]
              :out ?m}
             {:in [?rest-1 & ?tail]
              :out {?k [!vs ...] & ?m}}

             {:in [{} & ?tail]
              :out ?m}
             {:in ?tail
              :out ?m}

             {:in []
              :out ?m}
             ?m
             
             ?x
             ?x)]
    (if (= x x*)
      x
      (recur x*))))

(defn rule [x]
  ;; Make a "state" for processing.
  (rule* {:in x, :out {}}))

(rule '[{:key1 [val1]} {:key1 [val2]} {:key2 [val3]}])
;; =>
{:key1 [val2 val1], :key2 [val3]}

noprompt 2019-11-15T18:12:48.225700Z

AFAICT this isn’t a group-by though right?

noprompt 2019-11-15T18:12:55.226Z

Its like a group-by plus some post op.

snoe 2019-11-15T18:13:50.226400Z

yeah, I think that's the point group-by is never the end result

noprompt 2019-11-15T18:14:15.226800Z

Right.

noprompt 2019-11-15T18:15:31.227100Z

This should work (I think).

noprompt 2019-11-15T18:15:33.227300Z

(m/rewrite (group-by key (mapcat identity '[{:key1 [val1]} {:key1 [val2]} {:key2 [val3]}]))
  {?k [[_ [!vs ...]] ...] & (m/cata ?rest)}
  {?k [!vs ...] & ?rest}

  {?k ?v & (m/cata ?rest)}
  {?k ?v & ?rest})

noprompt 2019-11-15T18:16:40.227700Z

Second clause to handle other stuff.

snoe 2019-11-15T18:31:15.229800Z

When I first sat down with meander, my naive attempt /hope was that something like this might work.

(m/rewrite
  [{:key1 [1]} {:key1 [2]} {:key2 [3]}]
  [{[!k !v] ...} ...]
  {[!k !v] ... ::m/conflict into})
;=> {:key1 [1 2] :key2 [3]}

noprompt 2019-11-15T18:37:14.232Z

Maybe I should add that map-of operator eh?

noprompt 2019-11-15T18:37:29.232400Z

There’s also “tricks” like

(m/rewrite
  [{:key1 [1]} {:key1 [2]} {:key2 [3]}]
  [(m/seqable [!k !v] ...) ...]
  {& [[!k !v] ...] ::m/conflict into})
;; =>
{:key1 [2], :key2 [3], :meander.epsilon/conflict into}

noprompt 2019-11-15T18:37:52.232900Z

The {& [[k v] ...]} bit I need to jot down in the docs.

noprompt 2019-11-15T18:38:06.233300Z

Cause & on the right compiles to an into so you can do this.

noprompt 2019-11-15T18:38:36.234Z

The reason I haven’t made much of a mention of it is because, you should be able to write a similar thing on the left.

noprompt 2019-11-15T18:39:30.235200Z

But doing that is not as simple.

jimmy 2019-11-15T18:40:01.235700Z

The conflict idea is interesting @snoe. Might have to think about trying out something similar with defsyntax

noprompt 2019-11-15T18:49:44.237800Z

I think I’d be fine with adding that map-of operator. I probably should have added it a while back when I brought it up.

eraserhd 2019-11-15T18:57:01.239500Z

Thoughts on this case (m/match {:foo {:a 1, :b 2}} {?a {:a (m/some _)}, ?b {:b (m/some _)}} :ok)?

shaun-mahood 2019-11-15T19:13:31.245300Z

Here's a version of dealing with group-by that's a little more obvious (to me, at least) - my actual data has a slightly different shape than above. This gets me from [{:word "discovery", :strokes ["SKOEFR"]} {:word "discovery", :strokes ["SKOEUFR"]} ...] to ({:word "discovery", :strokes [["SKOEFR"] ["SKOEUFR"] ...]} ...)

(m/search (group-by :word ms)
            (m/scan [?word [{:strokes !stroke} ...]])
            {:word    ?word
             :strokes !stroke})

noprompt 2019-11-15T20:36:11.245900Z

This seems fine to me. Its easy to see what you’re doing though I don’t think you need to bind ?a or ?b (you could use _) but maybe :ok is just a temporary right side.

noprompt 2019-11-15T20:38:05.246900Z

@shaun-mahood Did you want the output to :strokes to be like that?

noprompt 2019-11-15T20:38:24.247400Z

Not sure if you wanted the result to be flat.

noprompt 2019-11-15T20:38:52.248300Z

(m/scan [?word [{:strokes [!strokes ...]} ...])

shaun-mahood 2019-11-15T20:41:29.250300Z

@noprompt Yeah, the different strokes can contain multiple strings in them as well so I need them as vectors for this one.

noprompt 2019-11-15T20:41:55.250700Z

Gotcha, well that’s definitely easy to understand.

noprompt 2019-11-15T20:42:49.251500Z

If its possible for :strokes to be absent you can change the value to be (m/or nil !stroke).

shaun-mahood 2019-11-15T20:43:03.251800Z

Ooh, that will be handy for some of the other things I need to do

noprompt 2019-11-15T20:43:35.252800Z

When using m/or with search, FYI, it will search each branch.

noprompt 2019-11-15T20:43:58.253Z

Its like conde pretty much.

noprompt 2019-11-15T20:44:04.253200Z

From miniKanren.

eraserhd 2019-11-15T20:44:47.253400Z

In this particular case, ?a and ?b are usually different, but we have some cases where they are the same.

eraserhd 2019-11-15T20:46:49.255300Z

I apparently gave a bad example. It should be m/search.

shaun-mahood 2019-11-15T20:46:57.255700Z

Ahh, cool.

eraserhd 2019-11-15T20:47:22.255800Z

In any case, I'm not convinced it should be different, I just wondered.

eraserhd 2019-11-15T20:48:39.256Z

oh, and we do need the bindings...

eraserhd 2019-11-15T20:49:03.256200Z

(m/search {:k {:a 1, :b 2}} {?a {:a (m/some _)} ?b {:b (m/some _)}} [?a ?b]) ;=> ()

noprompt 2019-11-15T20:58:36.256400Z

Is this the expectation?