Skip to content

Instantly share code, notes, and snippets.

@bhb

bhb/ex.txt

Created Sep 29, 2018
Embed
What would you like to do?
Interpreting `:in` paths in `clojure.spec`
> clj -Sdeps '{:deps {org.clojure/clojure {:mvn/version "1.10.0-alpha8"}}}'
Clojure 1.10.0-alpha8
user=> (require '[clojure.spec.alpha :as s])
nil
user=> ;; `explain-data` returns a description of the problem, including the `:in` path
user=> ;; which is the location of the invalid data within the context of the larger
user=> ;; data structure
user=> (-> (s/explain-data (s/coll-of int?) [1 "2"]) ::s/problems first)
{:path [], :pred int?, :val "2", :via [], :in [1]}
user=> (-> (s/explain-data (s/coll-of int?) [1 "2"]) ::s/problems first :in)
[1]
user=> ;; for instance, in the data above, the invalid data is located at path [1]
user=> ;; In simple cases, we can use our experience with `get-in` to understand
user=> ;; how the invalid data is found in the larger data structure
user=> (get-in [1 "2"] [1])
"2"
user=> ;; However, despite the similarity to paths compatible with `get-in`, these `:in`
user=> ;; paths don't work with `get-in` generally, since they also describe, for example, lists
user=> (-> (s/explain-data (s/coll-of int?) '(1 "")) ::s/problems first)
{:path [], :pred int?, :val "", :via [], :in [1]}
user=> ;; and this won't work with `get-in`
user=> (get-in '(1 "") [1])
nil
user=> ;; Another complication is that the meaning of the `:in` path depends on the
user=> ;; larger data structure. So, the same `:in` path can require totally different
user=> ;; access patterns depending on data structure. For example, here are four
user=> ;; calls to `explain-data` that all return the same `:in` path, but use different
user=> ;; collections:
user=> ;; 1. vectors in vectors
user=> (-> (s/explain-data (s/coll-of (s/coll-of int?)) [[] [:a]]) ::s/problems first :in)
[1 0]
user=> ;; 2. lists in lists
user=> (-> (s/explain-data (s/coll-of (s/coll-of int?)) '((1) (:a))) ::s/problems first :in)
[1 0]
user=> ;; (the meaning of the path is the same as example 1, we just can't use `get-in` directly)
user=> ;; 3. single-entry map
user=> (-> (s/explain-data (s/map-of keyword? int?) {1 1}) ::s/problems first :in)
[1 0]
user=> ;; (in spec, given a map `{:k :v}`, the `:in` path `[:k 0]` means "the path to the literal key `:k`"
user=> ;; whereas the `:in` path `[:k 1]` means "the path to the value assoced to `:k` i.e. :v".)
user=> ;; 4. multi-entry map
user=> (-> (s/explain-data (s/coll-of (s/tuple keyword? int)) {:a 1 "b" 2}) ::s/problems first :in)
[1 0]
user => ;; (the meaning here is to turn the map into a sequence of key-value pairs, and then use
user => ;; the path like normal i.e. you can roughly translate this into
user => ;; `(get-in (vec {:a 1 "b" 2}) [1 0])` )
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment