meander

All things about https://github.com/noprompt/meander Need help and no one responded? Feel free to ping @U5K8NTHEZ
noprompt 2020-12-15T01:23:57.461300Z

Some other things on the way that I will follow up with after the interpreter code • fresh which allows for fresh variables in the usual logic programming sense • project which allows for the current state of variables to be queried (actually way cooler than this) Example of fresh:

(m/match [1 2]
  [(m/fresh [?x]
     ?x)
   ?x]
  ?x)
;; => 2
Example of project:
;; (m/project <yield-pattern> <query-pattern> <value-pattern>)
;;
;; Attempts to yield an object described by yield-pattern using the
;; current bindings and match that object with query-pattern. If
;; successful, attempts to match the target object with value-pattern
;; if querying, or yield the object if yielding. Global bindings are
;; not altered when yielding the yield-pattern object.
(m/search 10
  ;; Capture two 10 in !x
  ;;   Bindings: {!x [10 10]}
  ;; Project the vector [!x !x] e.g. [10 10]
  ;; Match against [?x ?y]
  ;;   Bindings: {!x [10 10], ?x 10, ?y 10}
  ;; Match 10 against ?x
  (m/and !x !x (m/project [!x !x] [?x ?y] ?x))
  {:!x !x, :?x ?x, :?y ?y})
;; => ({:!x [10 10], :?x 10, :?y 10})

Lucy Wang 2020-12-15T14:00:32.462100Z

So fresh is like a block scope, in that case would m/scoped be slightly better than m/fresh ?

noprompt 2020-12-15T16:12:52.462400Z

I had a similar thought myself. 🙂 I’ve also been considering some other vocabulary words such as “local”, “ephemeral”, etc. to more accurately reflect the spirit. I will give this some more consideration!

jimmy 2020-12-15T19:45:01.462600Z

I love the idea of not going with fresh. Always confused me. I think scoped, but local makes the most sense to me.

noprompt 2020-12-15T19:53:52.462800Z

Lucy, what do you think of local? Also, sending to channel for feedback.

Lucy Wang 2020-12-18T09:26:08.106600Z

+1 for local, even better than scoped!

1👍
noprompt 2020-12-15T01:24:40.461900Z

Both of these will be usable on both sides.

Lucy Wang 2020-12-15T14:00:32.462100Z

So fresh is like a block scope, in that case would m/scoped be slightly better than m/fresh ?

noprompt 2020-12-15T16:12:52.462400Z

I had a similar thought myself. 🙂 I’ve also been considering some other vocabulary words such as “local”, “ephemeral”, etc. to more accurately reflect the spirit. I will give this some more consideration!

jimmy 2020-12-15T19:45:01.462600Z

I love the idea of not going with fresh. Always confused me. I think scoped, but local makes the most sense to me.

noprompt 2020-12-15T19:53:52.462800Z

Lucy, what do you think of local? Also, sending to channel for feedback.

dgr 2020-12-15T22:24:52.465700Z

I’m working on editing the Meander documentation. Can somebody give me more background on the differences between match and find? I understand the difference between match and search, but the only difference between match and findseems to be that I can use some pattern operations like scan in find, but not in match. Is that it? If so, why not just use find everywhere and do away with match? I assume that there is some sort of performance difference between the two, but it isn’t really mentioned. Is there anything else that find can do that match can’t, or vice versa?

1👏
jimmy 2020-12-15T22:30:08.467400Z

Match is unambiguous. We aren’t searching to find a way to satisfy the pattern. We are just matching very directly what was given. Given that we aren’t searching, match does have in general better performance characteristics. Find can do anything match can do. But match can’t use things that require searching to satisfy the pattern. Another way of putting it is that match allows there to only be exactly one match. Find allows there to be more than one, but selects the first.

jimmy 2020-12-15T22:30:37.467700Z

Also, thanks for any edits you are doing. Always wonderful to have people help out with that 🙂

noprompt 2020-12-15T22:30:52.467900Z

LOL just backspaced a bunch.

1🙂
noprompt 2020-12-15T22:32:10.468200Z

The semantics of find are basically (first (search ,,,)) but compiles to code specifically designed to find the first solution more efficiently.

dgr 2020-12-15T22:32:14.468400Z

So, define “searching” in the way that Meander uses the concept. How is what match does not searching while what search/`find` do is searching.

dgr 2020-12-15T22:32:30.468600Z

Yes, I get the diff between search and find. Totally makes sense.

dgr 2020-12-15T22:33:24.468800Z

It’s the diff between match and find (and search) that has me scratching my head.

noprompt 2020-12-15T22:33:32.469Z

match is searching in the traditional sense of pattern matching found in virtually every language that has it. Patterns must be unambiguous and match throws when there is no default and no match; pretty much core.match style.

noprompt 2020-12-15T22:33:58.469200Z

Both search and find allow for ambiguous patterns and neither complain when there is no solution found.

dgr 2020-12-15T22:34:18.469400Z

What does “ambiguous” mean? Just that more than one pattern matches?

dgr 2020-12-15T22:34:48.469600Z

OK, good point that neither search or find complain about nothing found. That’s important.

noprompt 2020-12-15T22:34:56.469800Z

An example of an ambiguous pattern would be {?k ?v} or {?k ?v, ?v ?k} because there is no one solution given a satisfactory map of the right shape.

dgr 2020-12-15T22:35:52.470Z

Hm. So how does it deal with {?k ?v}? What are the variables bound to?

noprompt 2020-12-15T22:36:09.470500Z

So a map like {:x 1 :y 1} in the first example could bind ?k to either :x or :y.

dgr 2020-12-15T22:36:30.470700Z

Got it. So does it just choose one?

dgr 2020-12-15T22:36:38.470900Z

The first?

noprompt 2020-12-15T22:36:47.471100Z

find does, search returns both answers.

noprompt 2020-12-15T22:36:59.471300Z

find just picks the first one blindly.

noprompt 2020-12-15T22:37:11.471500Z

Depth first.

dgr 2020-12-15T22:37:24.471700Z

OK, make sense. search returns both and find returns whatever search would find first.

noprompt 2020-12-15T22:37:59.472100Z

Another example would be [_ … ?x . _ …] aka (m/scan ?x). Some ?x is in there.

noprompt 2020-12-15T22:38:05.472400Z

Right.

noprompt 2020-12-15T22:39:13.472800Z

But you don’t want to call first on search because and prefer find because find avoids making collections etc.

noprompt 2020-12-15T22:39:28.473Z

Or, at least, it tries to avoid them as often as possible.

1👍
dgr 2020-12-15T22:39:50.473200Z

OK, got it. So basically, match wants things to have a single possibility. search is willing to have multiple possibilities, which it can deal with because it returns all of them. And find is effectively an optimized version of (first (search …)).

1👍
dgr 2020-12-15T22:40:58.474200Z

Thanks for the help. Another question so another thread. It seems like search (and find) are almost Prolog-like in how they accomplish the search. It seems it’s performing backtracking in some sort of search tree across the whole pattern(s). Is that a good way to think about it?

dgr 2020-12-15T22:55:28.477500Z

Does Meander return all results from search in a list instead of a lazy seq? In other words, does one ever have to worry about laziness, or is that a non-issue?

noprompt 2020-12-15T22:55:53.477700Z

It’s lazy.

noprompt 2020-12-15T22:56:03.478Z

So, don’t wrap it in binding 🙂

noprompt 2020-12-15T22:56:44.478900Z

find and match are, OTOH, eager.

dgr 2020-12-15T22:57:00.479300Z

Is it? I see: (class (m/search 1 1 :one)) ;; => clojure.lang.PersistentList

noprompt 2020-12-15T22:57:13.479500Z

That is probably not reliable.

noprompt 2020-12-15T22:58:28.480100Z

(let [x {:a 1 :b 2 1 :a :d 4}]
  (class
   (m/search x
     {?k ?v ?v ?k}
     x)))
;; => clojure.lang.LazySeq

dgr 2020-12-15T22:58:29.480200Z

Yep: (class (m/search [1 2 3 4] (m/scan ?x) ?x)) ;; => clojure.lang.LazySeq

1👍
dgr 2020-12-15T22:59:16.481Z

So, what can we say about the return type? It conforms to sequential? ??

noprompt 2020-12-15T23:01:58.481600Z

Yeah, I guess. Or that it matches the pattern (_ …).

noprompt 2020-12-15T23:03:20.482800Z

This the spec

(s/fdef meander.match.epsilon/search
  :args (s/cat :expr any?
               :clauses :meander.match.epsilon.match/clauses)
  :ret (s/coll-of any? :kind sequential?))

dgr 2020-12-15T23:03:25.483Z

Seems like it returns: nil (no match), PersistentList, and LazySeq, depending on the case.

noprompt 2020-12-15T23:03:34.483400Z

From meander.match.specs.epsilon

dgr 2020-12-15T23:03:48.484Z

OK, so it’s basically a sequential?.

noprompt 2020-12-15T23:03:53.484200Z

Yeah.

noprompt 2020-12-15T23:04:15.484900Z

BTW, specs are mostly in these “private” namespaces.

dgr 2020-12-15T23:04:26.485300Z

Should it return () for no match, instead of nil?

noprompt 2020-12-15T23:04:34.485600Z

Yes.

dgr 2020-12-15T23:04:36.485800Z

OK, I can take a look at the specs.

dgr 2020-12-15T23:05:36.487100Z

(m/search 0 1 :one) ;; => nil

dgr 2020-12-15T23:05:53.487300Z

not ()

dgr 2020-12-15T23:06:34.487700Z

and (sequential? nil) is false

noprompt 2020-12-15T23:06:59.488Z

Ah, well I guess then the spec is wrong. 🙂

dgr 2020-12-15T23:07:13.488600Z

LOL. 🙂 I just gave you another unit test.

noprompt 2020-12-15T23:07:21.488800Z

Eh, no.

noprompt 2020-12-15T23:07:35.489200Z

But, yeah, sure.

noprompt 2020-12-15T23:08:19.490600Z

Heh, I’m pretty sure would could figure out why its not a list because, I would actually agree there is merit to it having a () output at minimum.

jimmy 2020-12-15T23:08:20.490700Z

nil seems fine. And probably best not to change it now.

jimmy 2020-12-15T23:08:46.491300Z

We can fix the spec though

dgr 2020-12-15T23:08:50.491500Z

You want me to file a low priority bug? I don’t know that it would cause a problem very often, but it seems like empty list is better than nil.

jimmy 2020-12-15T23:09:29.492300Z

Yeah, but it is also a breaking change. Just imagine it in a when

noprompt 2020-12-15T23:09:48.493100Z

I agree. Yes you can file the ticket but here’s the thing: I really want to stop working on epsilon

dgr 2020-12-15T23:09:52.493200Z

Basically, a user should be checking for no result with (empty? (m/search …), right?

dgr 2020-12-15T23:10:10.493700Z

Understood. Do you have a way to tag it for zeta?

noprompt 2020-12-15T23:10:36.494300Z

In fact, I would like to personally stop working on epsilon, take the stuff that I like to zeta, integrate it and start building there.

noprompt 2020-12-15T23:10:45.494600Z

You can tag it as both.

noprompt 2020-12-15T23:10:56.495100Z

Because I think it should be fixed.

noprompt 2020-12-15T23:11:04.495400Z

I agree with your position. 🙂

dgr 2020-12-15T23:11:06.495500Z

OK, I’ll file it.

noprompt 2020-12-15T23:11:32.496400Z

Pointing out these flaws keeps me motivated.

dgr 2020-12-15T23:11:35.496500Z

Corner cases. Gotta love ’em. :face_with_rolling_eyes:

noprompt 2020-12-15T23:11:40.496700Z

Yah.

dgr 2020-12-15T23:11:59.497400Z

I’m a product manager for my day job, but I should have been a tester.

noprompt 2020-12-15T23:12:12.497900Z

Seriously the best way to see commits on the project is to come here and point out a thing or two 😅

noprompt 2020-12-15T23:12:20.498200Z

Semantics are important!

dgr 2020-12-15T23:12:29.498500Z

BTW, did you see my question about Meander search being sort of Prolog like? Did that make sense?

noprompt 2020-12-15T23:13:06.498900Z

Yes it does. search is basically like unify with a ground term.

dgr 2020-12-15T23:13:17.499100Z

Yep

dgr 2020-12-15T23:13:45.499800Z

OK, cool. Then it wasn’t my imagination. 🙂

noprompt 2020-12-15T23:14:20.000600Z

It’s kinda like conde 😎

dgr 2020-12-15T23:14:21.000800Z

And scan is something close to member/3 in Prolog.

dgr 2020-12-15T23:14:31.001100Z

Right

noprompt 2020-12-15T23:14:33.001200Z

Lemme check

noprompt 2020-12-15T23:15:39.001700Z

Not member/2? I just did a quick search and that’s all I see from the SWI docs.

dgr 2020-12-15T23:15:56.002100Z

Sorry, yea, member/2.

noprompt 2020-12-15T23:16:14.002600Z

Yes but it’s more like searching for a subsequence.

noprompt 2020-12-15T23:16:38.003600Z

(m/scan ?x ?y ?z) ~= [_ ... ?x ?y ?z . _ ...]

dgr 2020-12-15T23:16:46.003900Z

Right. It’s the backtracking that got me. So, even if you only have one pattern, you’re doing backtracking within that pattern if you’re using scan.

noprompt 2020-12-15T23:16:53.004100Z

Or rather particular elements of a subsequence.

noprompt 2020-12-15T23:17:05.004400Z

Yep.

dgr 2020-12-15T23:17:26.005200Z

And you’re calling the expression every time. Backtracking. Rebinding logic vars. Then calling the expression. And so on.

noprompt 2020-12-15T23:17:47.005700Z

Of course, we try to find the best way to create the smallest search space to minimize that by exploiting what we know.

dgr 2020-12-15T23:18:24.006400Z

BTW, I found something else while I was cooking up more examples for the docs.

noprompt 2020-12-15T23:18:27.006600Z

Yes, but again, we try to minimize that.

noprompt 2020-12-15T23:18:46.006900Z

Share away

dgr 2020-12-15T23:19:07.007200Z

(m/search {:a [1 2] :z [:x :y]} {:a (m/scan ?a) :z (m/scan ?z)} [?a ?z]) ;; => ([1 :x] [1 :y] [2 :x] [2 :y]) (m/search {:a [1 2] :b 2 :c 3 :d 4 :e 5 :f 6 :g 7 :h 8 :i 9 :j 10 :z [:x :y]} {:a (m/scan ?a) :b 2 :c 3 :d 4 :e 5 :f 6 :g 7 :h 8 :i 9 :j 10 :z (m/scan ?z)} [?a ?z]) ;; => ([1 :x] [2 :x] [1 :y] [2 :y])

noprompt 2020-12-15T23:19:37.007800Z

Yes

noprompt 2020-12-15T23:20:32.009400Z

Both patterns are looking for all possible combinations of ?a in [1 2] at :a and ?z in [:x :y] at :z.

dgr 2020-12-15T23:20:32.009500Z

Depending on the number of items in a map literal, Clojure will either put them in a PersistentArrayMap or a PersistentHashMap. If it chooses an array map, then the order is as the user writes them. If hash map, then it’s whatever the hash order is.

noprompt 2020-12-15T23:20:48.010200Z

And there is no way around that unfortunately.

dgr 2020-12-15T23:20:53.010500Z

That changes the order that Meander does the search and changes the order of the output.

dgr 2020-12-15T23:20:59.010800Z

It’s not a problem.

noprompt 2020-12-15T23:21:01.011Z

It can, yes,

noprompt 2020-12-15T23:21:09.011400Z

However, I have been thinking that is actually biased.

dgr 2020-12-15T23:21:13.011700Z

I just point that out in the (new) docs as something to be aware of.

dgr 2020-12-15T23:21:20.012100Z

It will affect find, for instance.

noprompt 2020-12-15T23:21:22.012200Z

We have ticket for the problem

noprompt 2020-12-15T23:21:29.012500Z

No solution yet.

dgr 2020-12-15T23:21:45.013100Z

Oh, you already have a ticket. Cool. No worries then.

noprompt 2020-12-15T23:21:53.013300Z

But I’ve been thinking a way to solve it would be to have a method of being able to tell the interpreter/compiler how to produce the space.

dgr 2020-12-15T23:22:42.014100Z

Most of the time it shouldn’t matter. And as long as it’s documented, I think it should be OK. I added a short section to the docs that shows the issue and basically says “Don’t rely on the ordering.”

dgr 2020-12-15T23:23:26.015Z

I gotta run and get dinner on the table for my kids. TTYL. Thanks for the help. That was great. I’ll file the bug shortly and I’ll submit a PR with doc updates in a bit.

noprompt 2020-12-15T23:24:11.015600Z

@droberts3 Thank you 🙏

noprompt 2020-12-15T23:24:55.015700Z

Good point @jimmy!