meander

All things about https://github.com/noprompt/meander Need help and no one responded? Feel free to ping @U5K8NTHEZ
jimmy 2020-05-04T15:53:50.187300Z

Sorry for the late reply. I will hopefully be able to answer this thoroughly today. It is actually something that comes up quite often so, I plan on adding some examples to the cookbook. Optionality is a little difficult just because it is context sensitive. The easiest (but often times not scalable) answer is to make multiple patterns that match with or with out it. Like I said, I should have an example later today.

noprompt 2020-05-04T19:30:43.187800Z

@pbaille Here’s an example:

;; [name doc-string? attr-map? [params*] prepost-map? body]
;; [name doc-string? attr-map? ([params*] prepost-map? body) + attr-map?]
(defn parse-defn [form]
  (m/rewrite form
    (m/with [%doc-and-rest (m/or ((m/pred string? ?doc-string) & %attr-map-and-rest)
                                 (m/let [?doc-string nil] %attr-map-and-rest))
             %attr-map-and-rest (m/or ((m/pred map? ?attr-map) & %fn-tail)
                                      (m/let [?attr-map nil] (& %fn-tail)))
             %fn-tail (m/or %fn-tail-1 %fn-tail-n)
             %fn-tail-1 (m/and ([& !params] & _) (m/let [?tail-attr-map nil]))
             %fn-tail-n (%fn-tail-1 ... & %tail-attr-map)
             %tail-attr-map (m/or {:as ?tail-attr-map} (m/let [?tail-attr-map nil] ()))]
      (`defn ?name & %doc-and-rest))
    {:name ?name
     :doc-string ?doc-string
     :attr-map ~(merge ?attr-map ?tail-attr-map)
     :fn-specs [{:params !params} ...]}))

["no doc, no attr map, 1 arity"
 (parse-defn '(clojure.core/defn foo [x y] 42))

 "doc, no attr map, 1 arity"
 (parse-defn '(clojure.core/defn foo "doc" [x y] 42))

 "no doc, attr map, 1 arity"
 (parse-defn '(clojure.core/defn foo {:doc "doc"} [x y] 42))

 "doc, attr map, 1 arity"
 (parse-defn '(clojure.core/defn foo "foo" {:doc "doc"} [x y] 42))

 "no doc, no attr map, n arity"
 (parse-defn '(clojure.core/defn foo ([x] 41) ([x y] 42)))

 "doc, no attr map, n arity"
 (parse-defn '(clojure.core/defn foo "doc" ([x] 41) ([x y] 42)))

 "no doc, attr map, n arity"
 (parse-defn '(clojure.core/defn foo {:doc "doc"} ([x] 41) ([x y] 42)))

 "doc, attr map, n arity"
 (parse-defn '(clojure.core/defn foo "foo" {:doc "doc"} ([x] 41) ([x y] 42)))]
;; =>
["no doc, no attr map, 1 arity"
 {:name foo,
  :doc-string nil,
  :attr-map nil,
  :fn-specs [{:params [x y]}]}
 "doc, no attr map, 1 arity"
 {:name foo,
  :doc-string "doc",
  :attr-map nil,
  :fn-specs [{:params [x y]}]}
 "no doc, attr map, 1 arity"
 {:name foo,
  :doc-string nil,
  :attr-map {:doc "doc"},
  :fn-specs [{:params [x y]}]}
 "doc, attr map, 1 arity"
 {:name foo,
  :doc-string "foo",
  :attr-map {:doc "doc"},
  :fn-specs [{:params [x y]}]}
 "no doc, no attr map, n arity"
 {:name foo,
  :doc-string nil,
  :attr-map nil,
  :fn-specs [{:params [x]} {:params [x y]}]}
 "doc, no attr map, n arity"
 {:name foo,
  :doc-string "doc",
  :attr-map nil,
  :fn-specs [{:params [x]} {:params [x y]}]}
 "no doc, attr map, n arity"
 {:name foo,
  :doc-string nil,
  :attr-map {:doc "doc"},
  :fn-specs [{:params [x]} {:params [x y]}]}
 "doc, attr map, n arity"
 {:name foo,
  :doc-string "foo",
  :attr-map {:doc "doc"},
  :fn-specs [{:params [x]} {:params [x y]}]}]

noprompt 2020-05-04T19:31:41.188600Z

The other way to write it is without with where you specify each case.

noprompt 2020-05-04T19:33:09.189Z

(defn parse-defn [form]
  (m/rewrite form
    (`defn ?name [& ?params] & ?body)
    {:name ?name
     :doc-string nil
     :attr-map nil
     :fn-specs [{:params ?params}]}

    ,,,))

noprompt 2020-05-04T19:37:43.191300Z

TBH — and I know no one here is soliciting my opinion — this is why I’m not a big fan of optionality: it breeds a lot of complexity.

noprompt 2020-05-04T19:38:28.192Z

There are actually 16 possible ways to write defn.

noprompt 2020-05-04T19:38:45.192200Z

16.

timothypratley 2020-05-04T19:44:08.192300Z

@pbaille Here is one way: https://github.com/timothypratley/justice/blob/master/src/justice/defn.cljc

timothypratley 2020-05-04T19:44:49.192600Z

FWIW If you compare it with the “spec driven approach” linked in the comment I think Meander is way more expressive 🙂

timothypratley 2020-05-04T19:45:37.192800Z

note that the way I emulated “optional” was to use a memory variable constrained to contain 1 or 0 elements

timothypratley 2020-05-04T19:45:54.193Z

I wish Meander had a built in operator for optional (hint hint @noprompt)

noprompt 2020-05-04T19:48:27.193200Z

@timothypratley The memory variable trick works too. I didn’t use it in the example. 🙂

timothypratley 2020-05-04T19:48:39.193400Z

sure but not all complexity is incidental 😛

jimmy 2020-05-04T19:48:53.193600Z

I tried to hack on in one time. But the compiler depends on the length of a pattern quite often to be able to do things intelligently. I found myself running into trouble because of that.

timothypratley 2020-05-04T19:52:08.193800Z

Also as a side topic; you can use tools analyzer to parse/emit normalized versions then match with meander to remove most optionality (happy to share examples on that too if interesting).

1👍
noprompt 2020-05-04T20:03:02.194Z

Just like all things, its probably worthwhile to use the word when e.g. When is complexity incidental? When you’re coding. When is it not? When you’re parsing. 🙂

timothypratley 2020-05-04T21:08:57.194300Z

:thumbsup: