meander

All things about https://github.com/noprompt/meander Need help and no one responded? Feel free to ping @U5K8NTHEZ
ingesol 2019-12-06T07:33:58.352100Z

@jimmy Got it! Thanks for your help, saved me tons of time.

🙌 1
ingesol 2019-12-06T14:08:30.356Z

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?

jimmy 2019-12-06T16:00:46.356300Z

(m/match [:a :a :a]
  [?x . (m/pred #{?x}) ...]
  ?x)

;; See better version below

jimmy 2019-12-06T16:02:25.357500Z

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

jimmy 2019-12-06T16:03:28.358300Z

Actually, I was stupid. Much easier way.

(m/match [:a :a :a]
  [?x . ?x ...]
  ?x)

jimmy 2019-12-06T16:04:09.358800Z

As soon as I posted it, I knew that there had to be an easier way.

ingesol 2019-12-06T16:07:44.359400Z

@jimmy Awesome, I suspected this would be an easy case for those list operators

ingesol 2019-12-06T16:07:57.359700Z

Working the learning curve, it’s not trivial 🙂

jimmy 2019-12-06T16:10:17.361200Z

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.

jimmy 2019-12-06T16:10:57.362100Z

Also, you can wrap the first ?x with (m/pred keyword? ?x) to ensure it is a keyword if that is important to you

dominicm 2019-12-06T16:11:41.363800Z

You can express some really complicated things with meander. I should try and translate some of them into normal clojure code for demonstration.

ingesol 2019-12-06T16:13:00.365Z

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

2019-12-06T16:20:39.365200Z

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))

dominicm 2019-12-06T16:23:55.365700Z

On mobile, but I'd go with when apply = all

2019-12-06T16:24:51.365900Z

👌

ingesol 2019-12-06T16:26:17.366100Z

(->> xs (filter identity) set count (= 1))
This was my code. It’s not much, but still it feels low level and not very pretty.

dominicm 2019-12-06T16:26:43.366300Z

(defn first-if-all-same
  [all]
  (when (apply = all) (first all)))

jimmy 2019-12-06T18:15:16.366500Z

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"

dominicm 2019-12-06T18:40:56.366800Z

Impressive

noprompt 2019-12-06T22:23:42.368100Z

I’m trying to get a release out today including the patches @jimmy and @dominicm handed me this week.

noprompt 2019-12-06T22:25:25.370100Z

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.

noprompt 2019-12-06T22:27:08.370700Z

So something like

(m/rewrite '(+ 1 0 3)
  (+ . !xs ...)
  (+ . (m/cata !xs) ...)

  ?x [?x])
won’t work.

noprompt 2019-12-06T22:27:34.371300Z

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.

noprompt 2019-12-06T22:35:36.372Z

Anyway, I’m trying to see if I can come up with something that resolves this issue.

noprompt 2019-12-06T22:36:58.373500Z

If I can’t come up with something, I’ll just have to make that an error at the moment.

noprompt 2019-12-06T22:37:26.374100Z

But I really don’t want to be lame, so I’m gonna try not to do that. 🙂

noprompt 2019-12-06T22:37:36.374400Z

Then I owe everyone an article on m/cata.

dominicm 2019-12-06T23:03:32.376Z

Yeah I'm confused by the whole thing tbh :)

dominicm 2019-12-06T23:03:57.376700Z

I just think of a code kata. I hope they're seriously powerful :)

jimmy 2019-12-06T23:05:33.377500Z

Catamorphisms are really cool. You can think about it like recursion that is done for you.

noprompt 2019-12-06T23:13:50.378800Z

@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]

noprompt 2019-12-06T23:14:35.379600Z

[(f ?x) (f ?y)] is basically applying the match recursively.

noprompt 2019-12-06T23:14:59.380Z

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]

noprompt 2019-12-06T23:15:56.381200Z

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.

noprompt 2019-12-06T23:16:47.381500Z

(m/match [1 2]
  [(m/cata :one) (m/cata :two)]
  [:ONE :TWO]

  1 :one
  2 :two
  _ :many)
;; => [:ONE :TWO]

noprompt 2019-12-06T23:22:01.383300Z

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.

noprompt 2019-12-06T23:24:18.384500Z

(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]]

noprompt 2019-12-06T23:25:14.384900Z

That’s probably a trash example.

noprompt 2019-12-06T23:25:17.385200Z

LOL

noprompt 2019-12-06T23:26:22.385900Z

m/cata is allowing you to recursively match/subst.

noprompt 2019-12-06T23:27:22.386700Z

I had a good tree example but I think its on another computer.

noprompt 2019-12-06T23:29:07.387300Z

Say you have something like

(m/rewrite '(let [x 1] 1)
  (let ?bindings & ?body)
  (let ?bindings (do & ?body)))
;; =>
(let [x 1] (do 1))

noprompt 2019-12-06T23:29:43.388200Z

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

noprompt 2019-12-06T23:30:19.388600Z

But with m/cata on the right you can say

noprompt 2019-12-06T23:30:21.388900Z

(m/rewrite '(let [x 1] 1)
  (let ?bindings & ?body)
  (let ?bindings (m/cata (do & ?body)))

  (do ?x)
  ?x)
;; =>
(let [x 1] 1)

dominicm 2019-12-06T23:31:12.389900Z

Ah, that's a better example.

dominicm 2019-12-06T23:32:18.391200Z

That might help me with my adjacency problems actually.

noprompt 2019-12-06T23:32:23.391500Z

(let ?bindings & ?body)
  (let ?bindings (m/cata (do & ?body)))
  ;;                     ^^^^^^^^^^^^
  ;; This substitution is applied before recursing.

noprompt 2019-12-06T23:33:05.392300Z

I’m close to having it work in general but I’m not happy with the state of the code that took me there.

noprompt 2019-12-06T23:33:16.392700Z

So, get it working and then fix it. 🙂

dominicm 2019-12-06T23:33:59.393100Z

Write it twice, at least :)

noprompt 2019-12-06T23:34:57.393500Z

Ha! Yeah 🙃

dominicm 2019-12-06T23:37:46.396300Z

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.