meander

All things about https://github.com/noprompt/meander Need help and no one responded? Feel free to ping @U5K8NTHEZ
yuhan 2020-01-03T00:18:45.214200Z

That's an interesting way of getting around it, feels slightly hacky but understandable due to the nature of EDN literals

yuhan 2020-01-03T00:21:43.214400Z

Going a step further for programmer safety I guess it should be possible to detect when "undefined behavior" like that is happening (same memory var being used in separate branches of a map/set literal, excluding &) and flag it as an error

yuhan 2020-01-03T00:23:35.214600Z

the example above might be contrived but I imagine it could happen with a big record-like map pattern and everything appearing to work fine until the 9th key is introduced

yuhan 2020-01-03T01:18:12.215100Z

sounds good, I'll create a GH issue to track it :)

1šŸ‘
yuhan 2020-01-03T01:20:30.215300Z

actually it's still a little confusing - here's a simpler case where memory vars currently fall apart:

(m/rewrite [:a [1 2 3] :b [4 5]]
  [!k [!n ...] ...]
  [!k [!n ...] ...])
;; => [:a [1 2 3 4 5]]

yuhan 2020-01-03T01:22:17.215500Z

how would I use the hypothetical &scope construct to retain the nesting associations and rewrite back to the same data on the RHS?

jimmy 2020-01-03T02:25:07.215700Z

They donā€™t fall apart, you just need more of them šŸ™‚

(m/rewrite [:a [1 2 3] :b [4 5]]
  [!k [!x ..!n] ..!m]
  [!k [!x ..!n] ..!m])
;; => [:a [1 2 3] :b [4 5]]

jimmy 2020-01-03T02:25:53.215900Z

My plan was to let you be able to just do ..! without naming them and make it all work out. I had an implementation, but never finished it and got it in.

yuhan 2020-01-03T02:35:52.216300Z

ahh, more undocumented syntax šŸ˜®

jimmy 2020-01-03T02:37:22.216500Z

Iā€™m shocked that didnā€™t get documented. My bad. I will start writing the documentation right this moment.

jimmy 2020-01-03T02:38:20.216700Z

And I will throw in some & documentation.

yuhan 2020-01-03T02:39:31.216900Z

Thanks!

jimmy 2020-01-03T02:55:31.217100Z

Not comprehensive, but hopefully it is a start. https://github.com/noprompt/meander/pull/98

jimmy 2020-01-03T02:59:37.217400Z

I do apologize of the lack of documentation on things like this. I need to sit down and write a comprehensive tutorial. But there is honestly just a lot. It is hard to remember what got documented and what didnā€™t. It is also just hard to document. It is more like learning a language than learning a library. Libraries are typically not too hard to document, but libraries require a different approach. I also need to finish my in browser meander evaluator so we can have an interactive cookbook that anyone can contribute to.

noprompt 2020-01-03T04:01:53.219900Z

I apologize as well. Iā€™m almost always busy with work, family, or something due to this project and documentation frequently slips my mind or doesnā€™t get priority.

noprompt 2020-01-03T04:02:52.221500Z

I will also admit that it often takes me a bit of time to write articles because I have to focus really hard to organize my thoughts into words.

noprompt 2020-01-03T05:31:27.222100Z

I really appreciate all the lively, thoughtful discussion from everyone. šŸ™‚

noprompt 2020-01-03T05:31:35.222300Z

Thank you!

timothypratley 2020-01-03T16:18:05.222400Z

OMG Iā€™m such an idiotā€¦ the ā€œlost informationā€ is completely solved by the ..!n

timothypratley 2020-01-03T16:18:21.222600Z

That is the associative information

timothypratley 2020-01-03T16:18:31.222800Z

I can easily reconstruct it from that

noprompt 2020-01-03T16:54:26.223700Z

That was the motivation for it. šŸ™ƒ I just forgot to mention it. šŸ˜‚

timothypratley 2020-01-03T23:33:46.224100Z

Ok so good news and bad news, the good news is I have coded a way to construct the ā€œinferred associativity of nested repeatsā€ or whatever you want to call them, the bad news is now Iā€™m going to start wanting to use these in substitutions. Here is my code:

timothypratley 2020-01-03T23:33:53.224300Z

(defn kvseq
  "Recursively convert a map to key/value pairs."
  [m]
  (for [[k v] m]
    [k (cond-> v (map? v) (kvseq))]))

(kvseq
 {1 {:en "one", :de "eins", :fr "un"}
  2 {:en "two", :de "zwei", :fr "deux" :es "dos"}
  3 {:en "three", :de "drei", :fr "trois"}
  5 {:fr "cinq"}})
#_#_=>
 ([1 ([:en "one"] [:de "eins"] [:fr "un"])]
  [2 ([:en "two"] [:de "zwei"] [:fr "deux"] [:es "dos"])]
  [3 ([:en "three"] [:de "drei"] [:fr "trois"])]
  [5 ([:fr "cinq"])])

(defn associations*
  "Not intended to be called directly, prefer using associations.
  Takes a sequence of outer rows"
  [result outer-rows inner-rows partitions]
  (if (seq outer-rows)
    (let [[outer-row & os] outer-rows
          [n & ns] partitions
          [inner-chunk is] (split-at n inner-rows)
          rows (for [inner-row inner-chunk]
                 (into outer-row inner-row))]
      (recur (concat result rows) os is ns))
    result))

(defn transpose
  "Given a sequence of columns, returns a sequence of rows, or vice versa."
  [seq-of-seqs]
  (apply map vector seq-of-seqs))

(defn seqable-of-seqables?
  [x]
  (and (seqable? x)
       (every? seqable? x)))

(defn associations
  "Takes sequences of outer values to be associated with sequences of inner values as rows by partitions.
  Rows are vectors of associated values.
  Given outer sequences ([1 2]),
  inner sequences ([:one :un :two :deux]),
  and partitions [2 2]
  Produces ([:one 1] [:un 1] [:two 2] [:deux 2])
  Suitable for converting memory variable notation into row notation.
  [!xs [!ys ..!ns] ...]
  (associations [!xs] [!ys] !ns)
  Can take layers of inners and partitions.
  [!xs [!ys [!zs ..!ms] ..!ns] ...]
  (associations [!xs] [!ys] !ns [!zs] !ms)
  => ([x y z] [x y z] [x y z] ...)
  By aligning inner and outer values, we define their relationship.
  This is often useful for deriving other logically consistent nested forms,
  For example {!xs... {!ys... !vs...}} -> rows -> {!ys... {!xs... !vs...}}
  Note that in this case, !ys and !vs are at the same level:
  (associations [!xs] [!ys !vs] !ns)
  => ([x y v] [x y v] [x y v] ...)"
  [outers & inner-partitions]
  {:pre [(even? (count inner-partitions))
         (seqable-of-seqables? outers)]}
  (loop [rows (transpose outers)
         ips inner-partitions]
    (if ips
      (let [[is ps & m] ips]
        (assert (seqable-of-seqables? is))
        (assert (= (count ps) (count rows)))
        (assert (seqable? ps))
        (assert (every? int? ps))
        (let [inner-rows (transpose is)]
          (assert (= (reduce + ps) (count inner-rows)))
          (recur (associations* () rows inner-rows ps)
                 m)))
      rows)))

(associations [[1 2]] [[:one :un :two :deux]] [2 2])
#_#_=> ([1 :one] [1 :un] [2 :two] [2 :deux])

(associations [[1 2]]
              [[:one :un :two :deux]] [2 2]
              [["ae1" "be1" "af1" "bf1" "ae2" "be2" "af2" "bf2"]] [2 2 2 2])
#_#_=>
    ([1 :one "ae1"]
     [1 :one "be1"]
     [1 :un "af1"]
     [1 :un "bf1"]
     [2 :two "ae2"]
     [2 :two "be2"]
     [2 :deux "af2"]
     [2 :deux "bf2"])

(def data {:en {1 "one", 2 "two", 3 "three"},
           :de {1 "eins", 2 "zwei", 3 "drei"},
           :fr {1 "un", 2 "deux", 3 "trois", 5 "cinq"},
           :es {4 "cuatro"}})

(m/match (kvseq data)
         ([!lang ([!num !word] ..!n)] ...)
         (associations [!lang] [!num !word] !n))
#_#_=>
    ([:en 1 "one"]
     [:en 2 "two"]
     [:en 3 "three"]
     [:de 1 "eins"]
     [:de 2 "zwei"]
     [:de 3 "drei"]
     [:fr 1 "un"]
     [:fr 2 "deux"]
     [:fr 3 "trois"]
     [:fr 5 "cinq"]
     [:es 4 "cuatro"])

(defn nest-by [order rows]
  (reduce
   (fn [acc ordered-row]
     (assoc-in acc (butlast ordered-row) (last ordered-row)))
   {}
   (for [row rows]
     (mapv row order))))

(nest-by [1 0 2]
         [[:en 1 "one"]
          [:en 2 "two"]
          [:en 3 "three"]
          [:de 1 "eins"]
          [:de 2 "zwei"]
          [:de 3 "drei"]
          [:fr 1 "un"]
          [:fr 2 "deux"]
          [:fr 3 "trois"]
          [:fr 5 "cinq"]
          [:es 4 "cuatro"]])
#_#_=>
    {1 {:en "one", :de "eins", :fr "un"},
     2 {:en "two", :de "zwei", :fr "deux"},
     3 {:en "three", :de "drei", :fr "trois"},
     5 {:fr "cinq"},
     4 {:es "cuatro"}}

(def data {:en {1 "one", 2 "two", 3 "three"},
           :de {1 "eins", 2 "zwei", 3 "drei"},
           :fr {1 "un", 2 "deux", 3 "trois", 5 "cinq"},
           :es {4 "cuatro"}})

(m/match (kvseq data)
         ([!lang ([!num !word] ..!n)] ...)
         (->> (associations [!lang] [!num !word] !n)
              (nest-by [1 0 2])))
=>
{1 {:en "one", :de "eins", :fr "un"},
 2 {:en "two", :de "zwei", :fr "deux"},
 3 {:en "three", :de "drei", :fr "trois"},
 5 {:fr "cinq"},
 4 {:es "cuatro"}}

timothypratley 2020-01-03T23:33:53.224500Z

timothypratley 2020-01-03T23:40:04.224900Z

:party-corgi:

timothypratley 2020-01-03T23:45:57.225100Z

Main things this proves to me: 1. Memory variables can capture the implied associativity of nested repeats (or whatever itā€™s called). 2. I think the concept of rows helps as an intermediary substrate when transforming between different orders of nesting 3. Creating a new nesting for maps is just assoc-in choosing the path and value from the rows in a different order 4. This is a general approach that works for arbitrary nestings of arbitrary participation (not just maps)

timothypratley 2020-01-03T23:49:37.225300Z

But the main drawback is of course that I had to write a lot of non-meander helper code. I think it is worth a quick review of why I needed them: