Fork me on GitHub
#meander
<
2020-03-12
>
yuhan09:03:35

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

yuhan09:03:18

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

yuhan09:03:07

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

yuhan09:03:32

something like:

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

yuhan09:03:06

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

yuhan09:03:44

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

yuhan09:03:58

just throwing out ideas here, I have no clue what the implementation dififculties are like 😅

jlmr15:03:59

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?

noprompt16:03:50

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

jlmr16:03:31

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

jlmr16:03:38

Does that make sense?

noprompt16:03:04

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]

  ,,,)

jlmr16:03:12

interesting

jlmr16:03:16

will try it out

noprompt16:03:45

@U56R03VNW 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}])

noprompt16:03:49

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.

noprompt16:03:31

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

noprompt16:03:56

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.

yuhan16:03:28

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.

yuhan16:03:52

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

noprompt16:03:17

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.

noprompt16:03:42

The name fold might also be inappropriate.

yuhan16:03:45

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

yuhan16:03:21

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

noprompt16:03:57

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 🙂

noprompt17:03:46

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

noprompt17:03:24

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

noprompt17:03:25

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

noprompt17:03:00

But the heart of reduce is the actual reducing function.

noprompt17:03:42

@qythium If you decide to play with zeta let me know if you have questions. 👍

jimmy17:03:58

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.

jimmy17:03:01

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

yuhan17:03:49

that's great to hear 🙂 I wonder if the current cata operator is somewhat of a misnomer too

jimmy17:03:39

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

yuhan17:03:31

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

yuhan17:03:40

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

yuhan17:03:56

lots of advanced macrology going on 😵

jimmy17:03:39

@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

jimmy17:03:57

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

jimmy17:03:56

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.

yuhan17:03:41

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

yuhan17:03:15

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

yuhan18:03:32

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

noprompt18:03:30

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

noprompt18:03:45

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.

noprompt18:03:23

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

noprompt18:03:56

If something doesn’t work its probably not implemented.

noprompt18:03:45

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.

jimmy19:03:02

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.

noprompt19:03:41

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

4
noprompt20:03:03

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

noprompt20:03:49

Well, for me anyway.

noprompt20:03:33

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.

☝️ 8
noprompt20:03:26

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.

noprompt20:03:35

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. 🙂

noprompt20:03:52

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.