This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2020-12-15
Channels
- # adventofcode (65)
- # announcements (25)
- # babashka (77)
- # beginners (194)
- # calva (15)
- # clj-kondo (39)
- # cljsrn (7)
- # clojure (81)
- # clojure-dev (4)
- # clojure-europe (46)
- # clojure-japan (1)
- # clojure-nl (8)
- # clojure-norway (2)
- # clojure-provo (4)
- # clojure-uk (17)
- # clojurescript (22)
- # clojurewerkz (1)
- # community-development (4)
- # conjure (2)
- # cryogen (2)
- # cursive (7)
- # datomic (3)
- # duct (3)
- # emacs (29)
- # events (11)
- # fulcro (88)
- # jobs (2)
- # jobs-discuss (68)
- # kaocha (10)
- # meander (119)
- # off-topic (7)
- # pathom (7)
- # perun (1)
- # planck (5)
- # portal (25)
- # re-frame (2)
- # reagent (10)
- # reitit (11)
- # remote-jobs (2)
- # reveal (96)
- # ring (4)
- # shadow-cljs (2)
- # sql (4)
- # tools-deps (46)
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})
So fresh is like a block scope, in that case would m/scoped
be slightly better than m/fresh
?
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!
I love the idea of not going with fresh. Always confused me. I think scoped, but local makes the most sense to me.
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 find
seems 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?
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.
Also, thanks for any edits you are doing. Always wonderful to have people help out with that 🙂
The semantics of find
are basically (first (search ,,,))
but compiles to code specifically designed to find the first solution more efficiently.
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.
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.
Both search
and find
allow for ambiguous patterns and neither complain when there is no solution found.
OK, good point that neither search
or find
complain about nothing found. That’s important.
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.
But you don’t want to call first
on search
because and prefer find
because find
avoids making collections etc.
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 …))
.
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?
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?
(let [x {:a 1 :b 2 1 :a :d 4}]
(class
(m/search x
{?k ?v ?v ?k}
x)))
;; => clojure.lang.LazySeq
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?))
Seems like it returns: nil (no match), PersistentList, and LazySeq, depending on the case.
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.
Yeah, but it is also a breaking change. Just imagine it in a when
Good point @U5K8NTHEZ!
nil seems fine. And probably best not to change it now.
We can fix the spec though
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.
I agree. Yes you can file the ticket but here’s the thing: I really want to stop working on epsilon
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.
Seriously the best way to see commits on the project is to come here and point out a thing or two 😅
BTW, did you see my question about Meander search
being sort of Prolog like? Did that make sense?
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
.
And you’re calling the expression every time. Backtracking. Rebinding logic vars. Then calling the expression. And so on.
Of course, we try to find the best way to create the smallest search space to minimize that by exploiting what we know.
(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])
Both patterns are looking for all possible combinations of ?a
in [1 2]
at :a
and ?z
in [:x :y]
at :z
.
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.
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.
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.”
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.
@droberts3 Thank you 🙏