@jimmy Got it! Thanks for your help, saved me tons of time.
New problem: I have a list of keywords. If all the keywords are the same value, I want to bind that value to a variable. Any ideas?
(m/match [:a :a :a]
[?x . (m/pred #{?x}) ...]
?x)
;; See better version below
There might be some other way to do that. But that's the first one I can think of. We capture the first one then use the fact that sets are functions to our advantage. This match will fail if any of them are different. @ingesol
Actually, I was stupid. Much easier way.
(m/match [:a :a :a]
[?x . ?x ...]
?x)
As soon as I posted it, I knew that there had to be an easier way.
@jimmy Awesome, I suspected this would be an easy case for those list operators
Working the learning curve, it’s not trivial 🙂
It's definitely not. Especially because you only really feel the need for a tool like meander once your data transformations are complicated. But in general, it is a very different way of thinking about things.
Also, you can wrap the first ?x
with (m/pred keyword? ?x)
to ensure it is a keyword if that is important to you
You can express some really complicated things with meander. I should try and translate some of them into normal clojure code for demonstration.
It’s always nice to be reminded how it feels to be a Clojure beginner, I guess it’s mostly the same 🙂 When converting some of my code to meander, it’s pretty clear what the benefits are. Readability of course, but also I can spot some ambiguities/bugs in my clojure code that simple become irrelevant when I’m not specifying the procedure
I was just thinking what the equivalent would be for this problem. Just spitballing:
(defn first-if-all-same [[first & rest]]
(if (every? (partial = first) rest)
first
nil))
On mobile, but I'd go with when apply = all
👌
(->> xs (filter identity) set count (= 1))
This was my code. It’s not much, but still it feels low level and not very pretty.(defn first-if-all-same
[all]
(when (apply = all) (first all)))
Turns out I introduced a bug that made the meander version not short-circuit. Made a PR to fix that. Once that is in the good thing is that for lazy seqs with the different one being at the front, and for lazy seqs with the difference being last, meander is as fast as @dominicm and @d4hines versions. For vectors where the last is different, meander is actually the fastest of these implementations. (Very unscientific benchmarks with large collections)
(let [last-different (into [] (concat (repeat 100000000 :a) '(:b)))]
(time (meander-first-if-all-same last-different))
(time (daniel-first-if-all-same last-different))
(time (ingesol-first-if-all-same last-different))
(time (dominicm-first-if-all-same last-different)))
"Elapsed time: 1477.152454 msecs"
"Elapsed time: 3386.322754 msecs"
"Elapsed time: 4628.184743 msecs"
"Elapsed time: 3802.695962 msecs"
Impressive
I was going to try and walk back some of the work that I did with getting m/cata
to work on the right side of m/rewrite
rules but I that seems like more work than I want to do and it works up to having an m/cata
with a memory variable in its substitution pattern repeated.
So something like
(m/rewrite '(+ 1 0 3)
(+ . !xs ...)
(+ . (m/cata !xs) ...)
?x [?x])
won’t work.But you can still do
(m/rewrite '(+ 1 0 3)
(+ . (m/cata !xs) ...)
(+ . !xs ...)
?x [?x])
;; =>
(+ [1] [0] [3])
for those kinds of simple cases.Anyway, I’m trying to see if I can come up with something that resolves this issue.
If I can’t come up with something, I’ll just have to make that an error at the moment.
But I really don’t want to be lame, so I’m gonna try not to do that. 🙂
Then I owe everyone an article on m/cata
.
Yeah I'm confused by the whole thing tbh :)
I just think of a code kata. I hope they're seriously powerful :)
Catamorphisms are really cool. You can think about it like recursion that is done for you.
@dominicm To build a basic intuition for it, consider something like
(letfn [(f [x]
(m/match x
[?x ?y]
[(f ?x) (f ?y)]
1 :one
2 :two
_ :many))]
(f [1 2]))
;; => [:one :two]
[(f ?x) (f ?y)]
is basically applying the match
recursively.
You can express the same thing with m/cata
(m/match [1 2]
[(m/cata ?x) (m/cata ?y)]
[?x ?y]
1 :one
2 :two
_ :many)
;; => [:one :two]
The difference with the m/cata
version is that the ?x
and ?y
are pattern matching on the result of the recursive pattern match e.g. ?x = :one
and ?y = :two
.
(m/match [1 2]
[(m/cata :one) (m/cata :two)]
[:ONE :TWO]
1 :one
2 :two
_ :many)
;; => [:ONE :TWO]
Having m/cata
work on the right side of rewrite
is almost the same but instead of the recursively matching on the value and then applying pattern matching to the result, you’re applying substitution to the argument and then recursively rewriting the result.
(m/rewrite [1 2 3 4]
[_ ?x & ?rest]
[(m/cata {:YAY ?x}) & (m/cata ?rest)]
[& ?x]
?x
{:YAY ?x}
[:I-GOT-YAY ?x])
;; =>
[[:I-GOT-YAY 2] [:I-GOT-YAY 4]]
That’s probably a trash example.
LOL
m/cata
is allowing you to recursively match/subst.
I had a good tree example but I think its on another computer.
Say you have something like
(m/rewrite '(let [x 1] 1)
(let ?bindings & ?body)
(let ?bindings (do & ?body)))
;; =>
(let [x 1] (do 1))
If you want to rewrite that (do 1)
to just 1
you’re either going to have to use match/find
or do some ugly thing with m/app
But with m/cata
on the right you can say
(m/rewrite '(let [x 1] 1)
(let ?bindings & ?body)
(let ?bindings (m/cata (do & ?body)))
(do ?x)
?x)
;; =>
(let [x 1] 1)
Ah, that's a better example.
That might help me with my adjacency problems actually.
(let ?bindings & ?body)
(let ?bindings (m/cata (do & ?body)))
;; ^^^^^^^^^^^^
;; This substitution is applied before recursing.
I’m close to having it work in general but I’m not happy with the state of the code that took me there.
So, get it working and then fix it. 🙂
Write it twice, at least :)
Ha! Yeah 🙃
Something I don't think can be expressed is partial rewiring. Say I want to match (m/sequence 1 ?x 3)
if I only want to rewrite the x to something and preserve the sequence type, that's quite difficult.
Or it is with my current understanding.
This is probably an edge case really anyway.