Fork me on GitHub
#meander
<
2020-05-04
>
noprompt19:05:43

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

noprompt19:05:41

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

noprompt19:05:09

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

    ,,,))

noprompt19:05:43

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.

timothypratley19:05:39

sure but not all complexity is incidental 😛

noprompt20:05:02

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

noprompt19:05:28

There are actually 16 possible ways to write defn.