meander

All things about https://github.com/noprompt/meander Need help and no one responded? Feel free to ping @U5K8NTHEZ
jimmy 2020-01-02T01:47:16.129600Z

@timothypratley Here’s an equivalent example just using meander.epsilon/search.

(m/search (fetch-as-hiccup company-directory-page)
  (m/$ [:div {:class "directory-tables"}
        . _ ...
        [:h3 _ ?department & _]
        _
        [:table &
         (m/$ [:tbody & 
               (m/scan [:tr
                        . _ ...
                        [:td {} & (m/scan [:a {} ?name & _])]
                        _ 
                        [:td {} ?title & _]
                        _
                        [:td {} _ [:a {:href ?mailto} & _] & _]
                        & _])])]
        & _])
  {:department ?department
   :name ?name
   :title ?title
   :email (subs ?mailto 7)})
I think removing unnecessary whitespace that is left in with the parse could help make this less finicky/messy. I didn’t do that just to show how to do the search with the exact same input. I used & a lot. (Which if it isn’t documented I will write up something tomorrow afternoon). One trick I did was to keep a & ?rest around as I was writing it so I could see what was left for me to search for. I used $ to jump into the nested structure to the points I cared about. If I were doing a lot of hiccup things I might use defsyntax to make something like tag, but I think the key really would be just doing preprocessing and stripping out whitespace. Would make everything cleaner.

timothypratley 2020-01-02T01:55:55.129700Z

oh wow, nice!!!

timothypratley 2020-01-02T02:10:08.129900Z

I noticed in the source code for epsilon there is a sibling syntax to $ called $* what does that do?

jimmy 2020-01-02T02:12:58.130100Z

If memory serves, the context argument is a function so you can apply the function to the context. But honestly not 100% sure

timothypratley 2020-01-02T05:29:46.130900Z

I took a shot at creating my own syntax for “find these things in order ignoring before, between and after”

timothypratley 2020-01-02T05:30:28.131Z

And along the way I got confused by this behavior:

timothypratley 2020-01-02T05:31:23.131200Z

(m/search [:a [1 "a"] :b [2 "b"] :c [3 "c"] :d]
          (m/scan [1 ?a] :b [2 ?b] :c [3 ?c])
          [?a ?b ?c])
;;=> (["a" "b" "c"])
^^ so far so good, that just proves that m/scan works

timothypratley 2020-01-02T05:32:46.131400Z

But then:

(m/defsyntax scan'
  [& patterns]
  (case (:meander.syntax.epsilon/phase &env)
    :meander/match
    (clojure.core/let [inner `(~@'(_ ...) ~@patterns ~@'(. _ ...))]
      `(seqable ~@inner))

    ;; else
    &form))

(m/search [:a [1 "a"] :b [2 "b"] :c [3 "c"] :d]
          (scan' [1 ?a] :b [2 ?b] :c [3 ?c])
          [?a ?b ?c])
=> nil
^^ this is copying m/scan definition to scan' but now it fails to match

timothypratley 2020-01-02T05:35:39.131800Z

I know the syntax is being called because I added prn debugging

timothypratley 2020-01-02T05:41:04.132Z

Oh gosh ignore all that, it should be m/seqable<-- didn’t realize that was a syntax

noprompt 2020-01-02T05:52:55.132400Z

[meander/epsilon "0.0.340"]

noprompt 2020-01-02T05:53:30.133Z

Thanks @timothypratley and @jimmy! 👍

timothypratley 2020-01-02T05:54:11.133100Z

So here’s my implementation:

(m/defsyntax scan*
  "Match patterns in order ignoring things around and between them.
  _ ... p1 . _ ... p2 . _ ... ,,, pn . _ ...
  where `p1` through `pn` are `patterns`."
  [&amp; patterns]
  (m/rewrite patterns
             ;; convert patterns to memory variables
             (!pattern ...)
             ;; substitute patterns interspersed with ignore anything
             (`m/seqable .
              '_ '... !pattern '. ...
              '_ '...)))

noprompt 2020-01-02T05:54:29.133300Z

@timothypratley There’s something like that currently called separated.

timothypratley 2020-01-02T05:54:34.133500Z

but it produces: => ([“a” “b” “c”] [“a” “b” “c”] [“a” “b” “c”] [“a” “b” “c”])

timothypratley 2020-01-02T05:54:58.134Z

(meander.epsilon/seqable _ ... [1 ?a] . _ ... [2 ?b] . _ ... [3 ?c] . _ ...)

timothypratley 2020-01-02T05:55:36.134200Z

oh right…

timothypratley 2020-01-02T05:55:55.134400Z

;; Prevent producing the same search results twice when ;; the target is a vector. Sounds relevant…

noprompt 2020-01-02T05:57:03.134700Z

I think the implementation could simply rely on seqable when the problem I mentioned about or in the seqable implementation goes away.

timothypratley 2020-01-02T05:57:08.134900Z

Hmmm same problem:

(m/search [:a [1 "a"] :b [2 "b"] :c [3 "c"] :d]
          (m/separated [1 ?a] [2 ?b] [3 ?c])
          [?a ?b ?c])
=&gt; (["a" "b" "c"] ["a" "b" "c"] ["a" "b" "c"] ["a" "b" "c"])

timothypratley 2020-01-02T05:57:29.135100Z

The output sould be (["a" "b" "c"]) as there is only one way to match the pattern

noprompt 2020-01-02T05:57:47.135300Z

Not exactly.

noprompt 2020-01-02T05:58:31.135500Z

Well, hmm…

noprompt 2020-01-02T06:02:22.135700Z

Yeah, thats probably a bug.

timothypratley 2020-01-02T06:02:58.135900Z

(m/search [1 2 3 4 5]
          [1 . _ ... 2 . _ ... ?x &amp; _]
          ?x)
=> (3 4 5 4 5 5)

timothypratley 2020-01-02T06:03:15.136100Z

^^ this seems like a good example without any syntax stuff

timothypratley 2020-01-02T06:03:21.136300Z

want me to open an issue?

noprompt 2020-01-02T06:03:50.136500Z

Yeah that makes sense though because the width of the last _ … grows to the point where the &amp; _ ends up nothing.

noprompt 2020-01-02T06:04:26.136700Z

(m/search [1 2 3 4 5]
  [1 . _ ... 2 . _ ... ?x &amp; ?rest]
  [?x ?rest])
;; =&gt;
([3 [4 5]] [4 [5]] [5 []] [4 [5]] [5 []] [5 []])

timothypratley 2020-01-02T06:04:30.136900Z

the answer should be (3 4 5) surely?

noprompt 2020-01-02T06:04:33.137100Z

And, yes, do open an issue on that

noprompt 2020-01-02T06:06:19.137300Z

(m/search [1 2 3 4 5]
  [1 . _ ... 2 . !part-a ... ?x . !part-b ...]
  {:part-a !part-a
   :x ?x
   :part-b !part-b})
;; =&gt;
({:part-a [], :x 3, :part-b [4 5]}
 {:part-a [3], :x 4, :part-b [5]}
 {:part-a [3 4], :x 5, :part-b []})

noprompt 2020-01-02T06:06:50.137500Z

So the bug seems to be with _ …

timothypratley 2020-01-02T06:07:22.137700Z

yeah it probably treats them all the same

noprompt 2020-01-02T06:08:05.137900Z

Yeah, they’re called “drops” :drp nodes and there’s some handling for patterns that use a lot of them. I can take a look at it tomorrow.

timothypratley 2020-01-02T06:08:24.138100Z

(m/search [1 2 3 4 5]
          [1 . !a ... 2 . !b ... ?x . !c ...]
          ?x)
=&gt; (3 4 5)

noprompt 2020-01-02T06:08:38.138300Z

Yeah.

noprompt 2020-01-02T06:08:59.138500Z

Okay, open a ticket for that.

noprompt 2020-01-02T06:09:06.138700Z

I can’t look at it tonight unfortunately.

timothypratley 2020-01-02T06:09:08.138900Z

:thumbsup: will do!

timothypratley 2020-01-02T06:09:13.139100Z

yeah no rush at all man

noprompt 2020-01-02T06:09:34.139300Z

But it probably has something do with the final case of :prt in match namespace that handles variable length on both sides.

noprompt 2020-01-02T06:09:51.139500Z

There’s a function in the syntax namespace called window and a couple other that are connected to it.

noprompt 2020-01-02T06:10:33.139700Z

Probably something off in there.

noprompt 2020-01-02T06:11:01.139900Z

Thanks for flagging these!

noprompt 2020-01-02T06:11:40.140100Z

I did a fair bit of pushing myself during the Clojurist Together sprint and made some mistakes along the way. 🙂

noprompt 2020-01-02T06:11:48.140300Z

Just gotta get them ironed out now! 😄

timothypratley 2020-01-02T06:12:31.140500Z

My pleasure! Hahaha well I’m really liking the new stuff 🙂

noprompt 2020-01-02T06:15:56.140700Z

Cool! I’m glad.

noprompt 2020-01-02T06:17:13.140900Z

https://github.com/noprompt/meander/issues/74

noprompt 2020-01-02T06:17:41.141200Z

☝️ I think we might want to roll in map-of / set-of soonish.

noprompt 2020-01-02T06:17:49.141400Z

Chime in if you have any thoughts on that one.

timothypratley 2020-01-02T06:21:07.141600Z

Oh lol, hear me out on one thing!!!

timothypratley 2020-01-02T06:21:14.141800Z

(m/search [1 2 3 4 5]
          [1 . !_ ... 2 . !_ ... ?x . !_ ...]
          ?x)

timothypratley 2020-01-02T06:21:24.142Z

^^ this produces the right result

timothypratley 2020-01-02T06:22:59.142200Z

hahahaha well it doesn’t really solve anything

timothypratley 2020-01-02T06:23:04.142400Z

it was just interesting

timothypratley 2020-01-02T07:24:40.142800Z

(m/search (fetch-as-hiccup company-directory-page)
             ;; match
             (m/$ [:div {:class "directory-tables"} &amp;
                   (m/scan
                    ;; match heading/table pairs
                    [:h3 {} ?department &amp; _]
                    _
                    [:table &amp;
                     (m/scan
                      [:tbody &amp;
                       (m/scan [:tr &amp;
                                (separated
                                 [:td &amp; (m/scan [:a {} ?name &amp; _])]
                                 [:td {} ?title &amp; _]
                                 [:td &amp; (m/scan [:a {:href ?mailto} &amp; _])])])])])])
             ;;=&gt;
             {:department ?department
              :name ?name
              :title ?title
              :email (subs ?mailto 7)})
^^ this relies on the syntax separated which is currently a little bit broken, instead I used my own definition:
(m/defsyntax separated
  "Match patterns in order ignoring things around and between them.
  _ ... p1 . _ ... p2 . _ ... ,,, pn . _ ...
  where `p1` through `pn` are `patterns`."
  [&amp; patterns]
  (m/rewrite patterns
             ;; convert patterns to memory variables
             (!pattern ...)
             ;; substitute patterns interspersed with ignore anything
             (`m/seqable .
              '!_ '... !pattern '. ...
              '!_ '...)))

yuhan 2020-01-02T07:26:51.143600Z

How would I bind a variable on the LHS to an expression dependent on a sub-pattern?

yuhan 2020-01-02T07:27:43.144600Z

I got there with let and a single logic variable, but can't figure out how to do it for a sequence

(m/match 10
  (m/and ?num
    (m/let [?parity (if (even? ?num) :even :odd)]))
  [?num ?parity])
;; =&gt; [10 :even]

yuhan 2020-01-02T07:28:28.144900Z

(m/match [0 1 2 5 8]
  [(m/and !num
     (m/let [!parity ??]))
   ...]
  [!num !parity])

yuhan 2020-01-02T08:01:09.146900Z

Intuitively the (let) form is in the place of a subpattern, so I feel like there should be a way to reference the value being matched in the binding vector

timothypratley 2020-01-02T08:03:26.147100Z

Yes I agree. Another idea:

(m/match [0 1 2 5 8]
         [!num ...]
         (let [!parity (mapv even? !num)]
           (m/subst
            [[!num !parity] ...])))

yuhan 2020-01-02T08:04:48.147300Z

That's confusing because !parity is being used as a regular Clojure binding

yuhan 2020-01-02T08:05:04.147500Z

not a Meander anaphoric-thing

yuhan 2020-01-02T08:06:55.147700Z

(m/match [0 1 2 5 8]
  [(m/and !num
     (m/or
       (m/pred even? (m/let [!parity :even]))
       (m/pred odd? (m/let [!parity :odd]))))
   ...]
  [!num !parity])
;; =&gt; [[0 1 2 5 8] [:even :odd :even :odd :even]]
This is a little closer in how m/pred operates implicitly on the "matched subvalue", but of course it won't scale to more complicated mappings

yuhan 2020-01-02T08:07:10.147900Z

perhaps I'll have to look into defsyntax

timothypratley 2020-01-02T08:07:26.148100Z

oh how about this:

(m/match [0 1 2 5 8]
         [(m/and !num
                 (m/app even? !parity)) ...]
         [!num !parity])

timothypratley 2020-01-02T08:08:08.148300Z

=> [[0 1 2 5 8] [true false true false true]]

yuhan 2020-01-02T08:08:10.148500Z

whoa, that was unexpected

timothypratley 2020-01-02T08:08:14.148700Z

hahah yeah

yuhan 2020-01-02T08:11:12.149Z

The context of this was issue #714 and brainstorming what an ideal syntax/semantics for "group-by" would look like

timothypratley 2020-01-02T08:13:11.149200Z

maybe I’m looking in the wrong place I don’t see that issue (max is 96)

timothypratley 2020-01-02T08:13:17.149400Z

can you please link to it?

yuhan 2020-01-02T08:13:26.149600Z

oops, #74

yuhan 2020-01-02T08:13:28.149800Z

https://github.com/noprompt/meander/issues/74

timothypratley 2020-01-02T08:13:35.150100Z

oh right!!!

yuhan 2020-01-02T08:14:07.150300Z

Ideally, I would like to write something like

(m/rewrite data
  (m/grouped-by !num (m/grouped-by !lang !word))
  (m/grouped-by !lang (m/grouped-by !num !word)))
for that example

yuhan 2020-01-02T08:17:07.150500Z

will probably sleep on it and post something on that issue, thanks for the help!

timothypratley 2020-01-02T08:41:48.150700Z

FWIW I think it would be nice if we could write

(m/rewrite data
           {!num {!lang !word}}
           {!lang {!num !word}})
^^ I think there is enough structure in this to infer the relationships between the variables (of course it produces an answer that is not what I want right now)

timothypratley 2020-01-02T08:44:39.150900Z

I think the mechanism has to be that nested sequences collect each symbol’s terms… ie: the above expression should not produce !num as an array, but rather ![num lang word] ie an array of tripples for substitution, and when substituting, the tripples then all align, but of course the map substitution would have to imply merge semantics (which I think is reasonable).

timothypratley 2020-01-02T08:45:19.151100Z

I guess 3 arrays of the same lenth is the same thing as an array of tripples so that detail doesn’t really matter

timothypratley 2020-01-02T08:49:33.151300Z

Consider:

(m/rewrite {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"}}
           (map-of !num (map-of !lang !word))
           [~(count !num) ~(count !lang) ~(count !word)])
=> [4 11 11] The problem here is that the arrays are different lengths, and we have no information for how to assemble them taking the right number from each

timothypratley 2020-01-02T08:50:14.151500Z

If the arrays were the same length, at least we would know the association between the values.

timothypratley 2020-01-02T08:50:56.151700Z

The current way that nested memory variables work obscures the associations.

yuhan 2020-01-02T16:08:02.151900Z

I think a separate operator would be better than further overloading the existing map syntax

yuhan 2020-01-02T16:09:48.152100Z

the pattern {!k !v} already has an existing interpretation and I imagine it would cause ambiguity

jimmy 2020-01-02T18:43:22.179200Z

The answer @timothypratley gave might seem weird, but I think if you think about it, it make sense.

(m/match [0 1 2 5 8]
  [(m/and !num
          (m/app even? !parity))
   ...]
  [!num !parity])
Compare this to:
(m/match [0 1 2 5 8]
  [(m/and !num !parity)
   ...]
  [!num !parity])
And says that the element should match both patterns. In this case our pattern is a memory variable which matches anything and stores it. Then we just combine that with an app and we can store the transformed value.

timothypratley 2020-01-02T07:33:31.145600Z

Does this help?

(m/search [0 1 2 5 8]
          (m/scan (m/and ?num (m/let [?parity (if (even? ?num) :even :odd)])))
          [?num ?parity])
=&gt; ([0 :even] [1 :odd] [2 :even] [5 :odd] [8 :even])
Or do you want the output to be [[0 1 2 5 8] [:even :odd :even :odd :even]]?

yuhan 2020-01-02T07:34:24.145900Z

hmm yes, search + scan works in this case, but I'd be interested to know if it could be done with !memory vars

yuhan 2020-01-02T07:38:40.146100Z

eg. if there is more than a single variable-length sequence in the overall pattern, using search and scan would produce all permutations of those sequences

yuhan 2020-01-02T07:40:21.146300Z

(m/match {:foo [1 2 3 4]
          :bar [5 6 7 8]}
  {:foo [!n ...]
   :bar [!m ...]}
  [!n !m])
;; =&gt; [[1 2 3 4] [5 6 7 8]]

(m/search {:foo [1 2 3 4]
           :bar [5 6 7 8]}
  {:foo (m/scan ?n)
   :bar (m/scan ?m)}
  [?n ?m])
;; =&gt; ([1 5] [1 6] [1 7] [1 8] [2 5] [2 6] [2 7] [2 8] [3 5] [3 6] [3 7] [3 8] [4 5] [4 6] [4 7] [4 8])

timothypratley 2020-01-02T07:51:21.146500Z

Would this help?

(m/match [0 1 2 5 8]
         (m/and [!num ...]
                (m/let [?parity (map even? !num)]))
         [!num ?parity])
^^ obviously this is kind of cheating hahaha

timothypratley 2020-01-02T07:52:22.146700Z

but it makes me wonder if the problem here is on the pattern or on substitution

yuhan 2020-01-02T17:21:50.153500Z

The more I think about it, memory variables seem unsound in general

yuhan 2020-01-02T17:21:59.153800Z

(m/rewrite [[:a 1] [:b 2] [:c 3]]
  [[!k !n] ...]
  [[!k !n (m/app str !n)] ...])
;; =&gt; [[:a 1 "2"] [:b 3 ""]]

yuhan 2020-01-02T17:22:53.155Z

This makes total sense operationally if you know that !n is just collecting and popping values

yuhan 2020-01-02T17:23:47.155700Z

but it seems at odds with the declarative nature of the library

timothypratley 2020-01-07T01:13:29.291500Z

Regarding “duplication”… I would have expected the solution in this case be be:

(m/rewrite [[:a 1] [:b 2] [:c 3]]
           [[!k !n] ...]
           [(m/let [?n !n]
                   [!k ?n (m/app str ?n)]) ...])

timothypratley 2020-01-07T01:14:23.291700Z

i.e. if I want to access !n once and use it twice, let seems like the tool. But … like most of my made up solutions… it doesn’t work.

timothypratley 2020-01-07T01:15:10.291900Z

Is there a way to do this?

jimmy 2020-01-07T15:04:45.006Z

We don't have a substitute version of let yet. You can do this, or duplicate the memory variable with (m/and !n !n2)

(m/match [[:a 1] [:b 2] [:c 3]]
  [[!k !n] ...]
  (let [?n !n]
    (m/subst
      [[!k ?n (m/app str ?n)] ...])))

timothypratley 2020-01-08T19:32:57.020400Z

Oh cool, I think I like (m/and !n !n2) more than the external let, and that should work for rewrite etc

timothypratley 2020-01-08T19:36:37.021700Z

Based on this line of thinking, I’m now confused by this:

timothypratley 2020-01-08T19:36:42.021900Z

(m/rewrite [[:a 1] [:b 2] [:c 3]]
           [[!k (m/and !n !n2)] ...]
           [[!k !n (m/app str !n2)] ...])

(m/rewrite [[:a 1] [:b 2] [:c 3]]
           [[!k (m/and !n (m/app str !n2)) ...]]
           [[!k !n !n2] ...])

timothypratley 2020-01-08T19:37:00.022100Z

The former works great, the latter does not

timothypratley 2020-01-08T19:37:27.022300Z

why?????!?!?!?!! 😕

timothypratley 2020-01-08T19:38:43.022500Z

FWIW I think the former is the better way to express it, as the transformation belongs in the target pattern, so this is a bit of a mooo point, just wanting to understand.

noprompt 2020-01-08T19:38:50.022700Z

@timothypratley because the second version has the in the wrong place.

noprompt 2020-01-08T19:39:09.022900Z

(= [[:a 1 "1"] [:b 2 "2"] [:c 3 "3"]]
   (m/rewrite [[:a 1] [:b 2] [:c 3]]
     [[!k (m/and !n !n2)] ...]
     [[!k !n (m/app str !n2)] ...])

   (m/rewrite [[:a 1] [:b 2] [:c 3]]
     [[!k (m/and !n (m/app str !n2))] ...]
     [[!k !n !n2] ...]))

timothypratley 2020-01-08T19:39:41.023100Z

oh right!!!

timothypratley 2020-01-08T19:39:45.023300Z

yeah thanks @noprompt

timothypratley 2020-01-08T19:40:14.023500Z

they both work 🙂

jimmy 2020-01-02T17:26:05.158100Z

There are a lot of cases where you want that behavior. For example making tuples of things. Or inverting a matrix. I definitely understand how it can be counter intuitive. But I'm not sure what the semantics would be otherwise.

yuhan 2020-01-02T17:31:24.160300Z

Yeah, I think it goes back to the "nesting" problem I was facing with the grouping construct - it's hard to differentiate where you want a variable to refer to a consistent value within a context, versus a sequence of things

yuhan 2020-01-02T17:32:41.161500Z

there's also the matter of mixing the sequential nature of !memory vars with unordered hash-maps

yuhan 2020-01-02T17:32:58.161900Z

(m/rewrite [1 2 3 4]
  [!n ...]
  {1 !n
   2 !n
   3 !n
   4 !n})
;; =&gt; {1 1, 2 2, 3 3, 4 4}

yuhan 2020-01-02T17:33:22.162200Z

but

(m/rewrite [1 2 3 4 5 6 7 8 9]
  [!n ...]
  {1 !n
   2 !n
   3 !n
   4 !n
   5 !n
   6 !n
   7 !n
   8 !n
   9 !n})
;; =&gt; {7 2, 1 6, 4 3, 6 9, 3 5, 2 1, 9 7, 5 8, 8 4}

yuhan 2020-01-02T17:36:59.164200Z

(I realise that's not a very realistic example, just trying to pick at the edges of the system to see if anything falls apart theoretically)

jimmy 2020-01-02T17:39:33.164700Z

Yeah there are a lot of interactions between these things. There are ways to accomplish that without having to worry about order. On my phone but can show later if needed.

noprompt 2020-01-02T18:45:31.180900Z

I would argue that memory variables are not actually at odds with declarative though. Really, its Meanders pattern evaluation strategy which is left to right, top to bottom.

timothypratley 2020-01-02T18:46:19.181900Z

An alternative universe of what a memory variable means: [thread incomming]

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-&gt; 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"}})
#_#_=&gt;
 ([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 &amp; os] outer-rows
          [n &amp; 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)
  =&gt; ([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...}} -&gt; rows -&gt; {!ys... {!xs... !vs...}}
  Note that in this case, !ys and !vs are at the same level:
  (associations [!xs] [!ys !vs] !ns)
  =&gt; ([x y v] [x y v] [x y v] ...)"
  [outers &amp; inner-partitions]
  {:pre [(even? (count inner-partitions))
         (seqable-of-seqables? outers)]}
  (loop [rows (transpose outers)
         ips inner-partitions]
    (if ips
      (let [[is ps &amp; 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])
#_#_=&gt; ([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])
#_#_=&gt;
    ([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))
#_#_=&gt;
    ([: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"]])
#_#_=&gt;
    {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)] ...)
         (-&gt;&gt; (associations [!lang] [!num !word] !n)
              (nest-by [1 0 2])))
=&gt;
{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:

timothypratley 2020-01-04T00:04:59.225500Z

a) kvseq was needed because there is no ..!n equivalent for maps b) associations is needed to convert memory variables and partitions to rows. I think this might be useful to meander users and hope you consider including something like it in meander. Is it actually as useful as I think it is? Maybe it doesn’t come up much, or maybe if we had substitution equivalents it would be unnecessary. c) nest-by is needed because meander doesn’t have a way of expressing reduction/aggregation.

timothypratley 2020-01-04T00:39:18.226600Z

It’s also very interesting to me that when you put the pattern and associations function side by side you can see clearly that one implies the other:

([!lang ([!num !word] ..!n)] ...)
(associations [!lang] [!num !word] !n)

jimmy 2020-01-04T00:54:28.226800Z

Haven't had a chance to deep dive on this. But you can match maps with seqable. And seqable supports ..!n.

timothypratley 2020-01-04T09:06:27.228900Z

Comparing with the search approach:

(m/search {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"}}
           (m/scan [?num (m/scan [?lang ?word])])
           [?lang ?num ?word])
^^ This finds all the rows without any helpers, has obvious ordering (which means nesting can be even simpler)… maybe the real answer here is you are better off not bothering with memory variables for associative transformations. There is a specific case where you have 2 repeats inside 1 repeat where you can’t use search/scan, but maybe that should be handled by first splitting then search/scanning each separately.

timothypratley 2020-01-05T03:52:21.251Z

hahaha check this out:

(m/match {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"}}
          {&amp; (m/seqable [*num {&amp; (m/seqable (m/app #(conj % *num) !lang-word-num) ...)}] ...)}
          !lang-word-num)
=&gt;
[[:en "one" 1]
 [:de "eins" 1]
 [:fr "un" 1]
 [:en "two" 2]
 [:de "zwei" 2]
 [:fr "deux" 2]
 [:es "dos" 2]
 [:en "three" 3]
 [:de "drei" 3]
 [:fr "trois" 3]
 [:fr "cinq" 5]]

yuhan 2020-01-05T03:56:37.251400Z

wow, mutable variables?

timothypratley 2020-01-05T03:57:14.251600Z

yeah! if the outer variables are mutable, you can collect them with the inner rows

yuhan 2020-01-05T04:02:41.251800Z

hmm, I've always wondered what those mutable vars could be used for

yuhan 2020-01-05T04:06:32.252Z

wait, they don't have to be mutable - I replaced *num with ?num and it worked fine

timothypratley 2020-01-05T04:08:57.252200Z

oh… interesting 🙂

timothypratley 2020-01-05T04:09:25.252400Z

I guess that makes sense 🙂

yuhan 2020-01-05T04:10:08.252600Z

I thought logic vars couldn't be used inside variable-length sequences

yuhan 2020-01-05T04:10:53.252800Z

but it seems like this is allowed because ?num doesn't "leak" outside the pattern?

timothypratley 2020-01-05T04:11:49.253Z

well, in this case it’s good that you can hahaha

timothypratley 2020-01-05T04:12:41.253200Z

But the final expression is pretty … ugly!!!

timothypratley 2020-01-05T04:14:47.253400Z

it could be a little cleaner if we made a thing called m/conj as shorthand for m/app conj

{&amp; (m/seqable [?num {&amp; (m/seqable (m/conj !row ?num) ...)}] ...)}

yuhan 2020-01-05T04:15:48.253700Z

yeah, it struck me as a little too "brain-teasery"

timothypratley 2020-01-05T04:16:56.253900Z

maybe even:

(m/map-of [?num (m/map-of (m/conj !row ?num))])

timothypratley 2020-01-05T04:21:29.254100Z

(m/into) would be more useful than (m/conj) and could work too

yuhan 2020-01-05T04:22:18.254300Z

Personally I like "map-of" to behave more closely to clojure.spec

timothypratley 2020-01-05T04:22:34.254500Z

how is that?

yuhan 2020-01-05T04:22:37.254700Z

where you have a key-pattern and val-pattern for a homogeneous map

yuhan 2020-01-05T04:23:23.254900Z

like the one which was previously proposed

timothypratley 2020-01-05T04:23:50.255100Z

right yes, in that case map-of won’t work here because the k/v need to be collected together

yuhan 2020-01-05T04:24:26.255300Z

yup, just a comment about the operator name

yuhan 2020-01-05T04:26:14.255500Z

> it could be a little cleaner if we made a thing called m/conj as shorthand for m/app conj… A more general shorthand might be nice too

(m/apply f !a ?b :c)
=&gt;
(m/app #(f %1 %2 :c) !a ?b)

timothypratley 2020-01-05T04:26:46.255700Z

Oh yeah, I like that.

timothypratley 2020-01-05T04:40:50.255900Z

{&amp; (m/seqable [?num {&amp; (m/seqable [?lang (m/and ?word (m/let [!row [?lang ?num ?word]]))] ...)}] ...)}

timothypratley 2020-01-05T04:40:58.256100Z

yet another way!!!

timothypratley 2020-01-05T04:41:43.256300Z

This one might work with map-of because it doesn’t rely on key-value pairing

timothypratley 2020-01-05T04:46:00.256500Z

ah nope: When matching, map patterns may not contain variables in their keys that would make it so there is more than one match possible.

timothypratley 2020-01-05T04:56:46.256700Z

(map-kvs ?num (map-kvs ?lang (m/and ?word (m/let [!row [?lang ?num ?word]]))))
^^ this works! 🙂

timothypratley 2020-01-05T04:56:59.256900Z

where

timothypratley 2020-01-05T04:57:03.257100Z

(m/defsyntax map-kvs [ks vs]
  (cond (m/match-syntax? &amp;env)
        `{&amp; (m/seqable [~ks ~vs] ...)}

        (m/subst-syntax? &amp;env)
        `{&amp; ([~ks ~vs] ...)}

        :else
        &amp;form))

timothypratley 2020-01-05T04:57:28.257300Z

similar to map-of but different in some way I can’t describe.

timothypratley 2020-01-05T05:10:01.257500Z

Oh interestingly the ?num trick does not work inside rewrite, but *num does… I have no idea why

timothypratley 2020-01-05T05:39:33.257700Z

O.K. one last riddiculous one:

(m/match {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"}}
          (map-kvs *num (map-kvs *lang (m/and *word (m/app (fn [word] (assoc-in (last !acc) [*lang *num] word)) !acc))))
          (last !acc))
=&gt;
{:en {1 "one", 2 "two", 3 "three"},
 :de {1 "eins", 2 "zwei", 3 "drei"},
 :fr {1 "un", 2 "deux", 3 "trois", 5 "cinq"},
 :es {2 "dos"}}

timothypratley 2020-01-05T05:43:31.257900Z

I’m abusing variables to perform a reduction while parsing… conceptually though this might not be crazy if there were a way to define an accumlator variable and something a little more slick than app for updating them.

timothypratley 2020-01-05T05:50:12.258100Z

I’m thinking of something like

(map-kvs *num (map-kvs *lang *word (m/collect **acc** assoc-in [*lang *num] *word)))
o_O

noprompt 2020-01-02T18:47:45.182600Z

Declarative programming simply boils down to idea that the programmer need not worry about control flow.

timothypratley 2020-01-02T18:50:06.183100Z

Consider for a moment a magical f* function:

(defn f* [m]
  (m/search m
            (m/scan [?k1 (m/scan [?k2 ?v])])
            [?k1 ?k2 ?v]))
Let’s use it:
(f* {:a {1 "a1"
         2 "a2"}
     :b {3 "b3"}})
=>
([:a 1 "a1"] [:a 2 "a2"] [:b 3 "b3"])
Wow!!! that’s useful 🙂 O.K. now get ready for even more awesome:
(defn f' [acc args]
  (assoc-in acc (butlast args) (last args)))
What is this f prime you ask??? Let’s see what it does:
(let [!associations '([:a 1 "a1"] [:a 2 "a2"] [:b 3 "b3"])]
  (reduce f' {} !associations))
=>
{:a {1 "a1", 2 "a2"}, :b {3 "b3"}}

timothypratley 2020-01-02T18:50:30.183300Z

What is going on here??

timothypratley 2020-01-02T18:51:22.183500Z

f*collected “variables” in an associative way which preserved information that current “memory variables” throws away

timothypratley 2020-01-02T18:51:59.183700Z

Specifically, when you nest memory variables, the collect values into arrays of different sizes, this explicitly removes the association information.

timothypratley 2020-01-02T18:52:26.183900Z

If in an alternate reality memory variables just did not work like this, we would have scoping.

timothypratley 2020-01-02T18:53:25.184100Z

What about f' ?? What’s the dealio there?

timothypratley 2020-01-02T18:54:12.184300Z

If we have “variables” in associative arrays, it becomes obvious how to substitute them associatively into a map: It’s just assoc-in!

timothypratley 2020-01-02T18:55:10.185Z

Now there isn’t any meander syntax sugar for assoc-in that I’m aware of right now, but in a world where we have “associative nested memory variables” this operator would be uber powerful.

timothypratley 2020-01-02T18:58:01.187700Z

I don’t find it hard to imagine

(m/rewrite {!k1 {!k2 !v}}                      
           {!k2 {!k1 !v}})
meaning exactly what it looks like: Collect k1 k2 v associatively, substitute as assoc-in k2 k1 v

timothypratley 2020-01-02T18:58:08.188Z

That

timothypratley 2020-01-02T18:58:11.188200Z

is

timothypratley 2020-01-02T18:58:21.188500Z

some seriously nifty syntax!

jimmy 2020-01-02T18:58:23.188700Z

I’ll take a different tact and say I don’t see this as non-declarative. Consider this example.

(m/rewrite [1 2 3 4 5 6]
  [!xs ...]
  [[!xs !xs] ...]
This can say “given a vector of zero or more elements, the result should be a vector with those elements in pairs”. I’m sure that @noprompt would probably say something about declarative being not 100% clear. Which is fine. But to me there is nothing undeclarative about memory variables. I will say that they can be surprising and there are certain things that are easy to accomplish with them and other things that are not so easy to accomplish with them.

timothypratley 2020-01-02T18:58:34.189Z

But it doesn’t have to us that syntax.

timothypratley 2020-01-02T18:58:40.189200Z

The core concept is the issue:

noprompt 2020-01-02T18:58:48.189600Z

The discomfort shown in the map examples are due to Clojure. There’s nothing a lot I can do about that. &amp; or m/and are the best offerings we have currently to get around the map ordering issue.

(m/and {1 !n} ,,, {9 !n})

{1 !n &amp; {2 !n &amp; {,,,}}}
I should point out this is completely not declarative… and I think thats okay because control flow in this way does have its merits.

timothypratley 2020-01-02T18:59:03.189800Z

How do you express collecting associative variables?

timothypratley 2020-01-02T18:59:38.190Z

We could introduce a new concept “associative memory variables” maybe even a new symbol

jimmy 2020-01-02T19:00:21.190200Z

If we say that [!n !n] meant repeat the exact same value twice, how do we get to the next value? Do we need to always say [!n ..2] or something like that? How does that work in the nested case? For example:

(m/rewrite [1 2 3 4 5 6]
  [!xs ...]
  {&amp; [[!xs [!xs !xs]] ...]})

timothypratley 2020-01-02T19:01:23.190400Z

FWIW I’d rather memory variables just work the way I want them to hahahahahaa but of course that’s my opinion

jimmy 2020-01-02T19:01:37.190600Z

For me, when I started thinking about those issues and how to solve them, the only answer I could come up with were explicitly imperative answers (like !xs.next).

jimmy 2020-01-02T19:02:47.190800Z

That said, we do need good ways to to accomplish duplication just like we need good ways of doing grouping. We also (I believe) need good ways to generalize memory variables.

noprompt 2020-01-02T19:04:53.191Z

Call me a pedant but I think that variables are always declarative.

noprompt 2020-01-02T19:07:27.191200Z

Memory variables were primarily driven to conception by regular expression things. I wouldn’t mind having an associative binding variable type, however.

timothypratley 2020-01-02T19:08:02.191400Z

They thing I want to discuss in this thread is that the nested memory variables drop information, useful information! If we had that information, we could do some cool stuff 🙂

jimmy 2020-01-02T19:10:40.192Z

I think we can generalize memory variables and do exactly what you are talking about. For a first approximation, memory variables are monoids. Why couldn’t we at least generalize that far?

noprompt 2020-01-02T19:11:14.192200Z

But memory variables were not designed for nested things. They were designed for sequential things. We’re simply lacking a type for nested/hierarchical things.

noprompt 2020-01-02T19:12:26.192400Z

I don’t think nesting is monoidal though.

jimmy 2020-01-02T19:12:37.192600Z

You can also do {&amp; [[1 !n] [2 !n] ,,, [9 !n]]}

jimmy 2020-01-02T19:16:18.192800Z

I would imagine a monoidal map could serve many of those purposes, but I admit I’m just spit balling.

jimmy 2020-01-02T19:19:38.193Z

Any examples of symbolic and imperative programming combined? I think imperative is useful, but seems at odds with symbolic and transparent ways of doing things.

noprompt 2020-01-02T19:24:32.193200Z

rewrite is that example.

noprompt 2020-01-02T19:25:19.193400Z

The structure of clauses are basically if then else.

jimmy 2020-01-02T19:32:50.193600Z

I guess we have different definitions of imperative. I don’t think if statements are an imperative construct.

noprompt 2020-01-02T19:34:27.193800Z

I don’t like the word imperative. Its not really helpful here. What I’m trying to say is there is utility in “ordering mattering” and “order not mattering”.

noprompt 2020-01-02T19:36:20.194100Z

What would this look like? I’m having trouble coming up with an sexpr kind of thing.

timothypratley 2020-01-02T19:45:03.194300Z

IMO it is way more elementary than any of that, its just fundamentally instead of producing different sized arrays, produce equal sized arrays. Maybe I’m guilty of jumping to an implementation here, but it just seems perfect 😛

noprompt 2020-01-02T19:45:32.194500Z

(m/rewrite [1 2]
  (&amp;scope-1 [?x ?y])
  (&amp;scope-1 ?x))
;; =&gt; 1

(m/rewrite [1 2]
  (&amp;scope-1 [?x ?y])
  (&amp;scope-1 ?y))
;; =&gt; 2

(m/rewrite [[1 2] [3 4]]
  [[(&amp;scope-1 [!x !y]) (&amp;scope-2 [!x !y])] ...]
  [(&amp;scope-1 !x) (&amp;scope-2 !x) . !x ...])
;; =&gt;
[1 3 1 3]

noprompt 2020-01-02T19:46:41.194700Z

Maybe I’m missing the thread but it seems like the desire is to have a notion of storing things in contexts.

noprompt 2020-01-02T19:48:46.194900Z

It seems entirely possible and useful to me to have a storage mechanism which is keyed by variables and their values and, in particular, for memory variables to have a distinct drain that belongs to that storage.

noprompt 2020-01-02T19:49:26.195100Z

In the third example above there is the global !xs and !ys and then there is the “scoped” !xs and !ys that belong to &amp;scope-1 and &amp;scope-2.

jimmy 2020-01-02T20:01:55.195300Z

(m/rewrite {!k1 {!k2 !v}}                      
           {!k2 {!k1 !v}})
@timothypratley Are you imagining an implicit repeat with this?
(m/rewrite {:a {:b :c}}
  {!k1 {!k2 !v}}                      
  {!k2 {!k1 !v}})
That does exactly what I’d expect, but I’m assuming you are thinking of it generalizing to where there is more than one key value pair in the outer (and inner?) structure?

timothypratley 2020-01-02T20:03:01.195600Z

I think you are presenting a (probably better) solution. I’m going to give a more direct example of what I’m pushing for specifically:

(m/match [[:a [[1 "a1"]
               [2 "a2"]]]
          [:b [[3 "b3"]]]]
         [[!k1 [[!k2 !v] ...]] ...]
         {:k1s !k1
          :k2s !k2
          :vs !v})
=>
{:k1s [:a :b], :k2s [1 2 3], :vs ["a1" "a2" "b3"]}
^^ This is how memory variables work, note that there are 2 memory variables gathered in !k1 But in Tim’s alternate universe: the result would be :
{:k1s [:a :a :b], :k2s [1 2 3], :vs ["a1" "a2" "b3"]}
Note that !k1 had 3 values in it!!! Just doing this one neat trick has preserved the associative information we crave, it defines the scopes implicitly.

noprompt 2020-01-02T20:03:05.195800Z

I’m happy to add map-of and set-of to epsilon so that folks can get this semantic easily.

timothypratley 2020-01-02T20:03:54.196Z

I’ll call them “tim variables” just to distinguish the behavior, but fundamentally its just about how the arrays get populated.

noprompt 2020-01-02T20:04:16.196200Z

LOL

timothypratley 2020-01-02T20:04:29.196400Z

map-of is a fine thing, but it doesn’t solve the information loss

noprompt 2020-01-02T20:04:35.196600Z

Its not clear to me how !ks1 ends up with two :as.

timothypratley 2020-01-02T20:04:44.196800Z

Aha! That’s easy

noprompt 2020-01-02T20:04:58.197Z

Oh I see.

noprompt 2020-01-02T20:05:18.197200Z

Yeah, I think the &amp;scope thing I mentioned gets at your problem.

timothypratley 2020-01-02T20:05:22.197400Z

when we add something “nested”, we just also add whatever te current parent is.

timothypratley 2020-01-02T20:06:30.197600Z

so one algorithm is: • don’t insert tim variables if there are subvariables. • when inserting a bottom level variable, insert all the parents

timothypratley 2020-01-02T20:06:47.197800Z

This might not be a great way, I’m just describing something that would mechanically make sense to me.

jimmy 2020-01-02T20:09:02.198Z

So what would the result of this be?

(m/match [[:a [[1 "a1"]
               [2 "a2"]]]
          [:b [[3 "b3"]]]]
         [[!k1 [[_ !v] ...]] ...]
         {:k1s !k1
          :vs !v})

noprompt 2020-01-02T20:10:48.198200Z

{!k1 ?x}
does not imply that ?x is a subvariable of !k1 . Consider
#{[!k1 ?x]}

noprompt 2020-01-02T20:11:32.198400Z

The idea of a subanything implies a super something.

noprompt 2020-01-02T20:12:40.198600Z

[!k1 ?x :as ?super]
This would be a case where there is a sub/super relationship.

timothypratley 2020-01-02T20:16:20.198900Z

@jimmy {:k1s [:a :a :b], :vs ["a1" "a2" "b3"]} @noprompt I don’t follow your notation. In my little world I’m only talking about sequences. Nested sequences are strictly identifiable. Map can always be represented as sequences. Maybe I confused things by eariler proposing a map syntax which isn’t really important to the question of how to keep context information. Also can you explain more what your $scope proposal is as I don’t understand the example.

timothypratley 2020-01-02T20:19:58.199100Z

Also just to brainstorm here, maybe we could use metadata on values in memory variables as a low friction alternative

jimmy 2020-01-02T20:20:29.199300Z

@timothypratley That’s what I figured. But I am a little confused by why. Is !k2 and !v are subvariables of !k1. But I am confused on the relationship between !k2 and !v. They seem to be siblings, so they would both be bottom in my mind and cause double inserts. Is that not their relationship? Is !v a bottom variable but not !k2?

timothypratley 2020-01-02T20:20:31.199500Z

well “on values” is probably the wrong word there

timothypratley 2020-01-02T20:22:53.199700Z

Correct, they are both bottom siblings, and yes that implies that regardless of how many siblings exist, insertion into the parent stack should happen only once.

timothypratley 2020-01-02T20:26:31.199900Z

to explore a as metadata style instead of equal array sizesapproach a bit more; When inserting a memory variable, check to see if there is a parent, and just record that somewhere. The question for that approach is how do you identify parents? By value? by index? and where do you store it? in a shadow array? as an array attached as metadata to the variable maybe? Seems entirely feasible.

noprompt 2020-01-02T20:30:15.200200Z

The hypothetical (&amp;scope &lt;pattern&gt;) establishes a storage location &amp;scope which maps variables in &lt;pattern&gt; to their values in &lt;pattern&gt; . Memory variables matched in a scope would be associated only with what the collected in that scope.

(m/match [1 2 3]
  [!xs (&amp;scope !xs) !xs]
  {:global-!xs !xs
   :&amp;scope-!xs (get &amp;scope '!xs)})
;; =&gt;
  {:global-!xs [1 2 3]
   :&amp;scope-!xs [2]}

noprompt 2020-01-02T20:31:51.200400Z

Of course we could’ve just written

[!xs (m/and !xs !ys) !xs]
and gotten the same affect.

timothypratley 2020-01-02T20:34:26.200600Z

I’m really confused, there is no sequence and no nesting 😕 😱

noprompt 2020-01-02T20:37:20.200800Z

Okay, lets back up here.

noprompt 2020-01-02T20:38:01.201Z

Lets go back to what I flagged earlier

[!xs !ys :as ?vector]

noprompt 2020-01-02T20:38:50.201200Z

If we were going to imagine a super/sub relation ship here, we could say that !xs and !ys are subvariables of ?vector in this example.

noprompt 2020-01-02T20:40:51.201400Z

But thats kind of weird so lets just say that !xs and !ys are subvariables of that vector.

noprompt 2020-01-02T20:41:44.201600Z

The !xs and !ys are siblings and thus one is not a subvariable of the other.

noprompt 2020-01-02T20:42:44.201800Z

The same is true if we talk about

{!ks !vs}
!ks is a sibling of !vs

noprompt 2020-01-02T20:43:20.202Z

Logically, they are members of a pair.

timothypratley 2020-01-02T20:43:26.202200Z

I’m taking about Subsequencevariables: [!x [!y …] …] Clearly for every x there can be 0-n y That seems like a different concept to what you are describing

noprompt 2020-01-02T20:44:01.202400Z

Right. And thats problematic if you want to keep things grouped.

noprompt 2020-01-02T20:45:02.202600Z

So when we want to keep things grouped we need mechanism for establishing a group; an context sensitive association thing.

timothypratley 2020-01-02T20:46:41.202800Z

I claim that I’ve described two mechanical ways to preserve the context sensitive information purely on the pattern 😛

timothypratley 2020-01-02T20:46:59.203Z

i.e. no syntax required to describe it.

jimmy 2020-01-02T20:47:58.203200Z

I think what @noprompt means is that if we want to be able to choose between “to group or not to group” we need a way to distinguish these two situations.

jimmy 2020-01-02T20:48:49.203600Z

Or if we want to group by something other than my parent

timothypratley 2020-01-02T20:49:11.203800Z

I don’t think we do need that; there are alternatives! 🙂 For example the metadata approach is just collecting more data… To chose to ignore it or use it happens later.

timothypratley 2020-01-02T20:49:41.204Z

So you don’t strictly need to choose that in the pattern being matched.

noprompt 2020-01-02T20:50:12.204200Z

You asked the right question earlier @timothypratley “how do you define what a parent is”?

timothypratley 2020-01-02T20:57:13.204400Z

Well taking the most basic case: [!x [!y …] …] Just by looking at it you can see that at the top level [_ _ …] has no sequence context around it, therefore !x has no “parent”. But when we look at !y it clearly occurs inside a surrounding sequence, so it has a “parent” !x. Programatically I imagine this logic as “I have a stack! Everytime I enter a sequence, I put that on the stack.

timothypratley 2020-01-02T20:57:58.204600Z

You don’t even have to think of them as parents at all really, just levels.

noprompt 2020-01-02T20:58:27.204800Z

You’re saying !x is !y s uncle.

noprompt 2020-01-02T20:58:30.205Z

Or aunt.

timothypratley 2020-01-02T20:58:51.205200Z

More simply I’m saying that sequences can be nested.

noprompt 2020-01-02T20:59:04.205400Z

But how would you square that notion with something like

[!x [!y] !x [!z]]
?

timothypratley 2020-01-02T20:59:14.205600Z

!x exists in a sequence, !y exists in a subsequence of that sequence.

timothypratley 2020-01-02T21:01:06.205800Z

there are no sequences in [!x [!y] !x [!z]] so nothing to square with that my small mind can see

jimmy 2020-01-02T21:01:28.206Z

By sequences, you mean repeats? (ie: …, ..2, ..!n, ..?n)

noprompt 2020-01-02T21:02:35.206300Z

What about

[!x [!y] !y [!x]]
?

timothypratley 2020-01-02T21:04:10.206600Z

I mean repeats yeah, I guess sequence is a really bad word hahahaha. Loops

noprompt 2020-01-02T21:05:34.206800Z

This is why I was saying earlier that the mechanism for expressing an binding scope is useful.

noprompt 2020-01-02T21:05:48.207Z

Right now there is only one scope: the global scope.

timothypratley 2020-01-02T21:06:25.207200Z

Every time there is a it implies a very useful scope 😛

jimmy 2020-01-02T21:15:39.207400Z

(m/match [[:a [[1 "a1"]
               [2 "a2"]]]
          [:b [[3 "b3"]]]]
  [[!k1 [[!k1.k2 !k1.v] ...]] ...]
  {:k1s !k1})

;; =&gt;

{:k1s [:a [{:k2 1 :v "a1"} {:k2 2 :v "a2"}]
       :b [{:k2 3 :v "b3"}]]}
Similarish idea, but not implicit. Not saying we should adopt that syntax, but gets the point across.

⭐ 1
timothypratley 2020-01-02T21:17:43.207600Z

Yeah! that preserves the associative information in a very precise way 🙂

timothypratley 2020-01-02T21:18:09.207800Z

gosh I need to make a new language up just to talk about this stuff

jimmy 2020-01-02T21:18:34.208Z

There is a book on constraint language based on term rewriting that has a very similar construct. I have a book on it at home.

timothypratley 2020-01-02T21:19:21.208200Z

“nested repeat stack associativity information” how’s that sound lol

timothypratley 2020-01-02T21:32:16.208400Z

Ummm so where in the code would I look for where insertion into memory variables occurs?

jimmy 2020-01-02T21:46:39.209Z

That change will be fairly difficult.

timothypratley 2020-01-02T21:46:57.209200Z

hahaha damnit!!!! well hopefully I’ll learn something.

timothypratley 2020-01-02T21:49:31.209400Z

Upon re-reading this thread I realize I never presented the KILLER FEATURE:

(defn f' [acc args]
  (assoc-in acc (reverse (butlast args)) (last args)))

(let [!associations '([:a 1 "a1"] [:a 2 "a2"] [:b 3 "b3"])]
  (reduce f' {} !associations))
=>
{1 {:a "a1"}, 2 {:a "a2"}, 3 {:b "b3"}}
Hopefully you’ll recognize this as a solution to @qythium’s language words transformation.

jimmy 2020-01-02T21:51:04.209600Z

You could possible make that work as a rewrite over the ir. That wouldn't require changing the internals.

noprompt 2020-01-02T22:26:30.210500Z

LOL with all the discussion today I didn’t get a chance to get into fixing the bug reported in #96.

timothypratley 2020-01-02T22:28:51.211Z

New thread in which ridiculous escapades are journaled:

timothypratley 2020-01-02T22:29:02.211100Z

(defmethod compile* :mvr-append
  [ir fail kind]
  (let [!symbol (:symbol ir)]
    `(let [~!symbol (conj ~!symbol ~(compile* (:target ir) fail kind))
           ~'_ (prn "APPEND" ~!symbol)]
       ~(compile* (:then ir) fail kind))))

(defmethod compile* :mvr-bind
  [ir fail kind]
  `(let [~(:symbol ir) ~(compile* (:target ir) fail kind)
         ~'_ (prn "BIND" ~(:symbol ir))]
     ~(compile* (:then ir) fail kind)))

(do (prn "----------")
    (meander.epsilon/match
     [:x1 [:y1 [:z1]
           :y2 [:z2 :z3]]
      :x2 [:y2 [:z4 :z5 :z6]]]
     [!x [!y [!z ...] ...] ...]
     [!x !y !z]))

timothypratley 2020-01-02T22:29:13.211300Z

=>

timothypratley 2020-01-02T22:29:19.211500Z

"----------"
"APPEND" [:x1]
"APPEND" [:y1]
"BIND" [:z1]
"APPEND" [:y1 :y2]
"BIND" [:z1 :z2 :z3]
"APPEND" [:x1 :x2]
"APPEND" [:y1 :y2 :y2]
"BIND" [:z1 :z2 :z3 :z4 :z5 :z6]
=&gt; [[:x1 :x2] [:y1 :y2 :y2] [:z1 :z2 :z3 :z4 :z5 :z6]]

timothypratley 2020-01-02T22:31:17.211700Z

what does append and bind mean? 😱 what things can I look up in ir?

jimmy 2020-01-02T22:36:48.212Z

Buried in some junk code I have a helper for analyzing stuff. https://github.com/jimmyhmiller/PlayGround/blob/master/wander/src/wander/core10.clj#L272

noprompt 2020-01-02T22:37:21.212300Z

:mvr-bind is for binding a memory variable, usually to a [] but sometimes to (vec seq).

jimmy 2020-01-02T22:37:24.212500Z

Not 100% accurate but useful.

noprompt 2020-01-02T22:37:33.212700Z

:mvr-append is for conjing on it.

noprompt 2020-01-02T22:39:10.212900Z

Sorry its not documented. 🙂

timothypratley 2020-01-02T22:41:31.213100Z

@jimmy ooo nice thank you I’ll play around with that @noprompt I’m probably conflating something but it seems surprising (in a wierdly good way) that my output indicates that !x and !y are appended to, but !z is bound to… I’m obviously misinterpreting the output though.

jimmy 2020-01-02T22:45:57.213300Z

Because z is just a literal vector. We can just bind it. It's basically an optimization.

timothypratley 2020-01-02T22:46:30.213500Z

ohic

yuhan 2020-01-02T23:46:24.213800Z

Just caught up with all the threads, I'm happy to have sparked so much discussion 😅

yuhan 2020-01-02T23:50:30.214Z

I really like the !k1.k2 notation and the idea of explicitly declared scopes