Fork me on GitHub
#specter
<
2017-02-14
>
nathanmarz02:02:05

@shader that first error was fixed in 0.13.2, as for second issue would need to see the code producing the error to help

zane19:02:53

@nathanmarz, I'm finding with-fresh-collected to be incredibly useful.

zane19:02:02

Here are two navigators I've used it for:

zane19:02:16

(s/def ::or-spec-path-args
  (s/cat :spec qualified-keyword?
         :branches (s/+ (s/alt :branch (s/cat :key simple-keyword? :body any?)
                               :invalid (s/cat :key #{:spec/invalid} :body any?)))))

(defmacro or-spec-path
  "Navigates to one of several subpaths depending on which branch of a
  `clojure.spec/or` the structure conforms to."
  [& args]
  (let [{:keys [spec branches] :as conformed} (s/conform ::or-spec-path-args args)
        spec-obj (s/get-spec spec)
        invalid (or (select-first (fn [[tag _]] (= :invalid tag))
                                  branches)
                    `(fn [val#] (throw (ex-info (s/explain-str ~spec val#)
                                                (s/explain-data ~spec val#)))))]
    (if-not spec-obj
      (throw (ex-info (str "Spec not found: " spec)
                      {:spec spec
                       :conformed conformed}))
      `(with-fresh-collected
         (collect-one (view (partial s/conform ~spec)))
         (cond-path
          (collected? [conformed#]
            (= ::s/invalid conformed#))
          ~invalid
          ~@(mapcat (fn [[_ {:keys [key body]}]]
                      `[(collected? [[tag# ~'_]]
                          (= tag# ~key))
                        ~body])
                    branches))))))

zane19:02:29

(s/def ::case-path-args
  (s/cat :path any?
         :branches (s/+ (s/cat :value any? :body any?))
         :default (s/? any?)))

(defmacro case-path
  "Navigates to one of several subpaths depending on the value selected by a the
  provided path."
  [& args]
  (let [{:keys [path branches default] :as conformed} (s/conform ::case-path-args args)]
    `(with-fresh-collected
       (collect-one ~path)
       (cond-path
        ~@(mapcat (fn [{:keys [value body]}]
                    `[(collected? [value#]
                        (= value# ~value))
                      ~body])
                  branches)
        ~@(when default
            `[(collected? [value#]
                true)
              ~default])))))

nathanmarz19:02:16

@zane cool, you should be able to do those as dynamic navs though

zane01:02:58

Hmm. Could I do the spec one as a dynamic nav? I want to have compile-time error reporting for that one.

nathanmarz04:02:01

I haven't dug into spec yet, so I don't understand every detail of your code. But if you did it as a dynamic nav it would throw the error the first time that callsite is run (when Specter's inline compiler expands dynamic navs).

nathanmarz04:02:51

Another approach you could take is have a macro that does your validation and expands to a dynamic nav invocation which does the rest of the work.

zane17:02:31

That last suggestion seems like the way to go. Thanks for the feedback!

nathanmarz21:02:11

opened a thread on the mailing list about whether Specter should be part of Clojure core or not https://groups.google.com/forum/#!topic/clojure/qN1UPMVQmaM

schmee21:02:17

I think it is better to have it as a lib since that way we can add and fix stuff faster and easier

dm322:02:12

it should probably be on a list of “blessed” libs, if one existed for Clojure

dm322:02:51

I think it’s in the same category as “core.async” - indispensable when you have a good problem for it