meander

All things about https://github.com/noprompt/meander Need help and no one responded? Feel free to ping @U5K8NTHEZ
grounded_sage 2020-02-12T11:49:23.177700Z

I’ll get back to this in a bit. Going through some other issues but being moved to data preprocessing soon so will be able to use Meander to it’s full potential.

markaddleman 2020-02-12T12:31:24.183600Z

I have a collection of maps where one of the keys in the maps is a timestamp. I am pattern matching over these maps using scan to filter for particular maps. I ultimately want to select only the most recent map. Something like this:

(m/search [{:ts 0 :m "hello" :j :k} {:ts 1 :m "goodbye" :j :k} {:ts 0 :n "joe" :j :k} {:ts 1 :n "sally" :j :k}]
          (m/and (m/most-recent-by ?ts1 (m/scan {:ts ?ts1 :m ?m :j ?v}))
                 (m/most-recent-by ?ts2 (m/scan {:ts ?ts2 :n ?n :j ?v}))
          [?m ?n]) => (["goodbye" "sally"]) 

markaddleman 2020-02-12T12:31:48.184200Z

I'm sure this will require a custom strategy but I'm lost on how to get started with that

jimmy 2020-02-12T15:36:54.186300Z

I'll have to think about that one a little. I have a way in mind I think will work, but will have to find the free time to try it out.

👍 1
noprompt 2020-02-12T17:36:50.189400Z

Something like

(m/and (m/scan {:time ?t :as ?m})
       (m/separated {:time (m/and ?t1 (m/guard (< ?t1 ?t)))}
                    ?m
                    {:time (m/and ?t2 (m/guard (< ?t2 ?t)))}))
might work.

markaddleman 2020-02-13T16:17:57.230500Z

fyi - The code at the top of this thread doesn't compile. I'm getting Unable to resolve symbol: ?t in this context from the guard clauses. Here's my full test case:

(m/search [{:time 1 :msg "hello"} {:time 2 :msg "goodbye"}]
            (m/and (m/scan {:time ?t :as ?m})
                   (m/separated {:time (m/and ?t1 (m/guard (<= ?t1 ?t)))}
                                #_?m
                                #_{:time (m/and ?t2 (m/guard (< ?t2 ?t)))}))
            ?m)

markaddleman 2020-02-13T16:39:20.230700Z

I was thinking about another approach: Is it safe to "exit" meander by using m/app and perform the reduction in Clojure-land? The thing that concerns me is that I believe that ends up assuming too much about Meander's runtime order of operations. In this case, I think it assumes that the m/scan is executed once and won't be revisited. In this trivial example it is but I don't know about more complex situations.

jimmy 2020-02-13T16:42:05.230900Z

That code not working definitely seems like a bug. Will see if I can find the time to track it down.

jimmy 2020-02-13T16:42:20.231100Z

I'm not 100% sure what you mean by scan only being executed once.

jimmy 2020-02-13T16:52:06.231300Z

I still consider the error you are getting a bug, but it can be fixed by moving the guards. That said, that code does not do what you want.

jimmy 2020-02-13T16:53:54.231500Z

You should be safe to use m/app for something like this. I did work on a reduction example using cata, but it is probably more than you want.

jimmy 2020-02-13T16:58:44.231700Z

I do think this is a good use case that we should make simpler.

markaddleman 2020-02-13T17:24:07.231900Z

I think part of my problem is that I don't have a good conceptual model for meander. I think of it as kind of a query system or as a simplistic constraint logic system (i know I'm wrong but I don't have a good feel for how wrong I am). Neither query and constraint systems would guarantee the order in which the scan would run or even how many times it would run. Since the result of m/app find-latest-data relies on processing the entire set of data, I got concerned.

markaddleman 2020-02-13T17:25:05.232100Z

I'll play around to see if I can get your example to compile by moving around the guards. Thanks a bunch for your help

noprompt 2020-02-13T18:25:44.232300Z

Sorry about this bug. guard kind of sucks in this regard because it depends on variables being bound and, to your point, Meander doesn’t have a declared evaluation model which results in bugs like this one. This is going to change in zeta where the model will always have the semantic left to right, top to bottom.

👍 1
noprompt 2020-02-13T18:27:32.232600Z

Even still, I’m looking at this again and am realizing its the wrong approach anyway.

jimmy 2020-02-13T19:21:06.232900Z

Without seeing a bit more of what sort of transformation you are doing in context, I can't be sure. But I would suggest trying things out, seeing how they work and if you find something you are not expected, we can definitely look at it. meander is kind like a query system and kind of like a constraint system. But for the most part, you shouldn't be worried about ordering and things like that.

👍 1
noprompt 2020-02-12T17:38:45.190500Z

But this will be slow.

jimmy 2020-02-12T17:39:12.191Z

That could work. But I feel like it would be rather slow right? I think the optimal solution would basically be a reduction. But also it seems that they want more than one if they are tied for minimal.

noprompt 2020-02-12T17:39:30.191200Z

Yeah. It will be slow for sure.

noprompt 2020-02-12T17:39:47.191400Z

But if the number of items is small it won’t be too big a deal.

noprompt 2020-02-12T17:40:08.191600Z

We need max etc.

jimmy 2020-02-12T17:40:16.191800Z

Yeah definitely. I think reducing with cata and building up the min elements would make sense.

markaddleman 2020-02-12T17:43:14.192Z

Slow isn't much of an issue initially. Under our initial rollout, multiple versions of maps is only a theoretical possibility but not likely. We'll see more versions of maps over the course of weeks.

markaddleman 2020-02-12T18:00:09.192200Z

btw, I'm curious about "slow" - Is this issue about algorithmic complexity or object generation overhead? I ask because this pattern is matching against data that is pulled from a firestore database. So, meander only needs to be faster than that IO

jimmy 2020-02-12T18:01:00.192400Z

Yeah I was meaning algorithmic complexity. You can do the operation in linear time if you do a reduction.

jimmy 2020-02-12T18:02:24.192600Z

Or n log n time if you just sort your collection.

markaddleman 2020-02-12T18:06:43.192800Z

Sorting the collection would be easy, of course. How would meander know to take advantage of an early exit from its looping?

markaddleman 2020-02-12T18:06:53.193Z

Or, is that part of the enhancement you were talking about?

jimmy 2020-02-12T18:10:13.193200Z

If the array was sorted you would just write a match that would take from the front until something changed. There would be no way for us to know that in general for that sort of match, but you wouldn't have to write match like that because you know it is sorted.

markaddleman 2020-02-12T18:12:26.193400Z

This "time-based" join is part of a larger pattern. Of course, I could break up each join into its own match if necessary.

markaddleman 2020-02-12T18:13:26.193600Z

It would be awesome to have something conceptually like clojure's reduce functionality with its "reduced" feature

markaddleman 2020-02-12T18:35:53.194400Z

Oh, that's awesome. I didn't really understand the problem that cata was solving until that example

micha 2020-02-12T21:34:26.194900Z

how do recursive patterns work?

jimmy 2020-02-12T21:37:11.197400Z

We can talk about recursive patterns. But that is failing because your pattern says that enabled should be true and you want that to repeat. If instead you wanted to filter for only the true ones you can use gather. (on my phone so can't give an example right now)

micha 2020-02-12T21:37:37.197700Z

ah i didn't see gather, i'll check the source

jimmy 2020-02-12T21:40:27.198300Z

(e/gather {:enabled? true :thingid !things} ...)

micha 2020-02-12T21:41:15.198500Z

works, thanks!

noprompt 2020-02-12T23:24:53.200400Z

Essentially, gather is just

(seqable (or <pattern> _))
which is short hand for
(or [(or <pattern> _) ...] ((or <pattern> _) ...))
and is primarily useful in instances like these.