meander

All things about https://github.com/noprompt/meander Need help and no one responded? Feel free to ping @U5K8NTHEZ
yuhan 2020-03-12T09:11:35.169700Z

I was thinking a little about the proposed fold operator in zeta, something doesn't feel right about the mutable variable

yuhan 2020-03-12T09:13:18.171400Z

Shouldn't the process of folding be an implementation detail that's hidden from the user? I can't think of any case where the intermediate values of the accumulator would want to be referred /outside/ of the fold

yuhan 2020-03-12T09:19:07.172700Z

It seems more natural for the (fold ...) form to match the entire sequence to be reduced, and bind its argument as a general pattern to the result of the reduction

yuhan 2020-03-12T09:20:32.173200Z

something like:

(m/match [1 8 9 -1 10 30 3]
  (m/fold ?result 0 clojure.core/min)
  ?result)
;; => -1

yuhan 2020-03-12T09:40:06.175500Z

if anything, there could be a 5-arity of the operator (fold result init acc elem fn-return) where everything except fn-return is a pattern

yuhan 2020-03-12T09:43:44.175800Z

{:total-score ?total
 :bonus ?bonus
 :games (m/fold [?total ?min-score] ?bonus
          [*t *min] {:score *s} [(+ *t *s) (min *min *s)])}

yuhan 2020-03-12T09:45:58.176600Z

just throwing out ideas here, I have no clue what the implementation dififculties are like šŸ˜…

jlmr 2020-03-12T15:57:59.180600Z

Hi, Iā€™m trying to decide if meander is the right tool to use. Iā€™ve never used it before, so I have some trouble deciding on its applicability. I have a sequence of maps like this:

(def data
  [{:a 1 :b 0 :c 0 :score 0.0123}
   {:a 0 :b 1 :c 0 :score 0.0123}
   {:a 1 :b 0 :c 1 :score 0.0123} ...])
Right now Iā€™m trying to create different groupings of these maps using code like this:
(defn has-kvs?
  [m subset]
  (let [create-predicate (fn [[k v]] (fn [x] (v (get x k))))
        pred (apply every-pred (map create-predicate subset))]
    (pred m)))

(let [preds {"Pred 1" #(or (has-kvs? % {:a pos?
                                        :b zero?
                                        :c zero?})
                           (has-kvs? % {:b zero?
                                        :c zero?}))}

            "Pred2" #(or (has-kvs? % {:a zero?
                                      :b pos?})
                         (has-kvs? % {:a zero?
                                      :c pos?}))]

    (for [[title pred] preds]
      {:title title :values (->> data
                                 (filter pred)
                                 (map :score))}))
It could be that this minimal example doesnā€™t make complete sense, but in the full code it works, although it is quite cumbersome writing the predicate combinations. Could meander help with that in some way?

noprompt 2020-03-12T16:16:31.186800Z

@qythium On zeta they are not that hard and, in fact, there is already a working implementation of the fold idea. Having the match happen at the ā€œendā€ of the match presents a bit of a challenge but its an interesting suggestion. Iā€™ll think about it. Another form I had considered was

(fold *var init clauses ,,,)
where clauses would be shaped liked
[current input] output
The other thing I should let you in on, which might give you more context, is that fold has a dual on the RHS which is that it ā€œunfoldsā€. (Note that I used the word ā€œdualā€ and not inverse.) In the unfold scenario we work from the current value of *var down to init by using the dual of the reducing function.

noprompt 2020-03-12T16:19:56.190Z

The ā€œmain ideaā€ for zeta is to have the LHS and RHS be duals of each other in everyway. All of the primitives like and, or, let, etc. will work on the RHS.

noprompt 2020-03-12T16:21:50.190600Z

We can give it a shot. Given your input data what are you thinking you would like your output to look like?

jlmr 2020-03-12T16:24:31.191Z

Hm, maybe something like:

[{:title "group name 1" :scores [0.0123 0.0123]}
 {:title "group name 2" :scores [0.0123 0.0123]}]
And somewhere else should be defined when a score belongs to a group name

jlmr 2020-03-12T16:24:38.191200Z

Does that make sense?

noprompt 2020-03-12T16:30:04.191400Z

The first thing I might suggest would be to write something which matches your records and does the bucketing. Maybe something like this

(match x
  {:a 1 :b _ :c _ :score ?score}
  ["one-in-column-a" ?score]

  {:a _ :b 1 :c _ :score ?score}
  ["one-in-column-b" ?score]

  ,,,)

jlmr 2020-03-12T16:32:12.191600Z

interesting

jlmr 2020-03-12T16:32:16.191800Z

will try it out

noprompt 2020-03-12T16:38:45.193600Z

@jlmr Hereā€™s something that connects more with the problem you described

(defn record-buckets [record]
  (m/search record
    (m/or {:a (m/pred pos?) :b 0 :c 0}
          {:b 0 :c 0})
    ["Pred 1" record]

    (m/or {:a 0 :b (m/pred pos?)}
          {:a 0 :c (m/pred pos?)})
    ["Pred 2" record]))

(mapcat record-buckets [{:a 1 :b 0 :c 0 :score 0.0123}
                        {:a 0 :b 1 :c 0 :score 0.0123}
                        {:a 1 :b 0 :c 1 :score 0.0123}])
;; =>
(["Pred 1" {:a 1, :b 0, :c 0, :score 0.0123}]
 ["Pred 1" {:a 1, :b 0, :c 0, :score 0.0123}]
 ["Pred 2" {:a 0, :b 1, :c 0, :score 0.0123}])

noprompt 2020-03-12T16:42:49.196700Z

search works to find all the possible solutions that match your input. If you want only the first match you would switch to m/find and use map instead of mapcat.

yuhan 2020-03-12T16:43:28.197300Z

I went back to look at the given example :

(m/find [1 8 9 -1 10 30 3]
  (m/with [%min (m/fold *min 0 clojure.core/min)]
    [%min ...])
   *min)
;; => -1
and it just occured that the fold form is supposed to take the role of "reducing step" (?) , hence the use of with etc.

yuhan 2020-03-12T16:45:52.199600Z

It's still quite unintuitive how that's supposed to work... maybe I need to set up a zeta scratchpad to try things out

noprompt 2020-03-12T16:48:17.201Z

Yes. fold is a primitive which, in essence, manages the binding. Theres a couple of smaller pieces missing on zeta but once they are there you could just them to derive logic and memory variables from the same structure.

noprompt 2020-03-12T16:49:42.201700Z

The name fold might also be inappropriate.

yuhan 2020-03-12T16:53:45.202500Z

That might be partly it, based on the name I was expecting a pattern which stood in place of a collection to be folded

yuhan 2020-03-12T16:55:21.203900Z

Also my category theory knowledge is a little shaky but does it somehow correspond to catamorphisms on the LHS and anamorphisms on the RHS?

noprompt 2020-03-12T16:58:57.205500Z

Yes, but Iā€™ll be honest I donā€™t have enough CT skills to go in depth on it. Though I can say thereā€™s this cool thing: http://conal.net/talks/folds-and-unfolds.pdf šŸ™‚

noprompt 2020-03-12T17:01:46.206500Z

I should have some time soon to fill in some of the meander.zeta namespace for folks to play around with.

noprompt 2020-03-12T17:03:24.207900Z

All of the work that is being done on zeta is happening in dev/meander/ and src/meander/runtime

noprompt 2020-03-12T17:07:25.210300Z

Its worth mentioning that Clojureā€™s reduce has a very specific implementation: its driven by seqable things only.

noprompt 2020-03-12T17:08:00.210800Z

But the heart of reduce is the actual reducing function.

noprompt 2020-03-12T17:15:42.213200Z

@qythium If you decide to play with zeta let me know if you have questions. šŸ‘

jimmy 2020-03-12T17:17:58.216900Z

I definitely think the current fold we are playing around with is a bit lower level than people will need to actually use. Being able to match on the result of a fold with a logic variable definitely makes sense. I think the fold we currently have is the primitive that will power that sort of thing. For example, we could actually implement memory variables in terms of fold, the really are just syntatic sugar for a fold with empty vector and conj.

jimmy 2020-03-12T17:20:01.218200Z

Really the mutable variable in fold doesn't even need to be exposed. (mutable is really a misnomer for this imo)

yuhan 2020-03-12T17:25:49.219100Z

that's great to hear šŸ™‚ I wonder if the current cata operator is somewhat of a misnomer too

jimmy 2020-03-12T17:26:39.220Z

Cata on the lhs I think is cata. Cata in rhs is really recur. (Had it backwards originally)

yuhan 2020-03-12T17:33:31.222200Z

Any hints on how to use zeta in its current state? I checked out the branch and started a repl clj -A:dev but the namespaces are doing weird things when loaded

yuhan 2020-03-12T17:35:40.223600Z

overwriting the compiled/*.clj files and throwing "unmatched delimiter" errors

yuhan 2020-03-12T17:36:56.224200Z

lots of advanced macrology going on šŸ˜µ

jimmy 2020-03-12T17:41:39.225Z

@qythium Donā€™t know your setup. But I just pulled down the latest. Loaded up cider with the -A:dev and evaluated the meander.dev.zeta namespace

jimmy 2020-03-12T17:41:57.225500Z

Everything worked for me doing a simple (rewrite 1 ?x ?x)

jimmy 2020-03-12T17:45:56.226800Z

There is definitely a lot of weird stuff going on. But I will say, so far working with the meander.zeta compiler is so much better. I am going to have a blog post coming out soon sketching the idea. Not full details, but showing how to make your own meander lite compiler using meander.

yuhan 2020-03-12T17:52:41.228Z

That's really strange, it works in a plain clj repl in the terminal but not when I use Cider

yuhan 2020-03-12T17:55:15.228900Z

it's alright, I'll figure it out later - probably something to do with my Emacs config

yuhan 2020-03-12T18:02:32.229500Z

aha, it was due to my *print-length* settings, the compiled output got truncated

noprompt 2020-03-12T18:17:30.230200Z

Thats good to know. That means we should probably rebind it in the defmodule source.

noprompt 2020-03-12T18:19:45.232400Z

defmodule is basically meander.epsilon/rewrite where the result of its macro expansion is rewritten to use the zeta runtime and the source is dumped to a file where it is defned.

noprompt 2020-03-12T18:21:23.233600Z

The subst compilation emits to code which uses only the runtime, match compilation is somewhere in between.

noprompt 2020-03-12T18:21:56.233900Z

If something doesnā€™t work its probably not implemented.

noprompt 2020-03-12T18:23:45.235700Z

The last time I was in there I was starting to work on m/string and that ultimately caused me to realize that weā€™re missing greedy star/plus.

jimmy 2020-03-12T19:50:02.237100Z

This setup definitely seems strange. But there are lots of reasons we went down this route. One being that meander.zeta will eventually be bootstrapped so our optimization effort will yield both faster generated code and a faster compiler at the same time.

noprompt 2020-03-12T19:59:41.238200Z

Its also, like, 10,000 times easier to work on the parser.

āž• 1
noprompt 2020-03-12T20:02:03.239200Z

Its been a breeze to work on the parser and, really, the other components too.

noprompt 2020-03-12T20:02:49.240Z

Well, for me anyway.

noprompt 2020-03-12T20:04:33.241700Z

A reason for that is due to not having to manage a bunch of functions or deal with the hassle of doing things manually with Clojure. Using recursive rewrite rules is just so much simpler and easier to me because I only have to think of shapes. Pretty much the value proposition of rewrite.

ā˜ļø 2
noprompt 2020-03-12T20:08:26.245800Z

To put it in perspective, I spent several weekends writing the zeta parser in Clojure before dumping it because it was frustrating to change and debug. I rewrote it with rewrite and had a working, bug free implementation in less than a day, really about a few hours.

noprompt 2020-03-12T20:10:35.247700Z

Now, because Jimmy and I have been the only ones to be working on zeta there isnā€™t much in the way of commentary explaining how things got to the point they are at on zeta, however, I am (and Iā€™m sure Jimmy is too) very happy to discuss and collaborate on any of it. šŸ™‚

noprompt 2020-03-12T20:12:52.249100Z

One of the biggest advantages to this approach, in general ā€”Ā again, to me ā€” is the very minimal amount of scope. In day-to-day programming in most every language theres just this deluge of scope.