This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2016-06-03
Channels
- # admin-announcements (2)
- # alda (4)
- # beginners (15)
- # boot (89)
- # cljs-dev (88)
- # cljsrn (75)
- # clojure (149)
- # clojure-belgium (16)
- # clojure-france (2)
- # clojure-greece (6)
- # clojure-russia (108)
- # clojure-spec (39)
- # clojure-taiwan (3)
- # clojure-uk (7)
- # clojurescript (70)
- # css (3)
- # cursive (17)
- # data-science (2)
- # datascript (7)
- # datomic (41)
- # dirac (3)
- # hoplon (12)
- # instaparse (1)
- # juxt (3)
- # lambdaisland (9)
- # mount (4)
- # off-topic (6)
- # om (71)
- # om-next (4)
- # onyx (22)
- # other-languages (56)
- # perun (15)
- # proton (6)
- # re-frame (32)
- # reagent (42)
- # specter (34)
- # spirituality-ethics (7)
- # tmp-json-parsing (5)
- # untangled (13)
- # vim (4)
- # yada (6)
I'm having some trouble spec'ing a fn that walks maps allowing you to hyphenate keys etc. I wonder if someone can point out my mistake as I'm having a hard time decrypting the error message…
(defn walk-map
"Recursively apply a function to all map entries. When map is nil returns an
empty map."
[f m]
(if m
(walk/postwalk (fn [x] (if (map? x) (into {} (map f x)) x)) m)
{}))
(s/fdef walk-map
:args (s/cat :f (s/fspec :args (s/tuple ::s/any ::s/any) :ret ::kv)
:m ::maybe-any-map)
:ret ::any-map)
(defn hyphenate-keys
"Recursively transforms all map keys from underscored strings to hyphenated
keywords."
[m]
(walk-map (fn [[k v]] [(hyphenated-keyword k) v]) m))
(s/fdef hyphenate-keys
:args (s/cat :m ::maybe-any-map)
:ret ::any-map)
The error:
ERROR in (t-hyphenate-keys) (core.clj:4631)
expected: (= (sut/hyphenate-keys {:a 1, "a" 2}) {:a 2})
actual: clojure.lang.ExceptionInfo: Call to #'example.common/walk-map did not conform to spec:
In: [0] val: ({}) fails at: [:args :f] predicate: (apply fn), nth not supported on this type: PersistentArrayMap
:clojure.spec/args (#object[example.common$hyphenate_keys$fn__20568 0x754a38a0 "ex[email protected]"] {:a 1, "a" 2})
With the s/cat
that I'd expect I need:
ERROR in (t-walk-map) (core.clj:4631)
expected: (= (sut/walk-map (fn [[k v]] [(name k) (if (number? v) (inc v) v)]) {:a 1, :b {:c 2, :d {:e 3}}}) {"a" 2, "b" {"c" 3, "d" {"e" 4}}})
actual: clojure.lang.ExceptionInfo: Call to #'example.common/walk-map did not conform to spec:
In: [0] val: ([[] []]) fails at: [:args :f] predicate: (apply fn), clojure.lang.PersistentVector cannot be cast to clojure.lang.Named
:clojure.spec/args (#object[example.common_test$fn__21912$fn__21926 0x7787a602 "example.common_te[email protected]"] {:a 1, :b {:c 2, :d {:e 3}}})
(s/fdef walk-map
:args (s/cat :f (s/fspec :args (s/cat :kv ::s/any) :ret ::kv)
:m ::maybe-any-map)
:ret ::any-map)
Hmm.This is what's confusing me. This spec looks right, but doesn't work in the fspec
s :args
.
(s/explain-data (s/cat :kv (s/tuple ::s/any ::s/any)) [[:a {:b 2}]])
what is your hyphenated-keyword
fn doing? I’m not sure I’m following what’s happening there, but it looks like it’s blowing up in that function, and that’s causing the spec failure
(defn- hyphenated-keyword
[x]
(if (or (string? x) (keyword? x))
(-> x keyword->string infl/hyphenate keyword)
x))
@manutter51: I've used this code in a number of projects for a few years now. The fns are pretty reliable.The relevant specs I'm using:
(s/def ::any-map
(s/map-of (s/nilable ::s/any) (s/nilable ::s/any)))
(s/def ::maybe-any-map
(s/nilable ::any-map))
In the walk-map
test I get a cast exception:
ERROR in (t-walk-map) (core.clj:4631)
expected: (= (sut/walk-map (fn [[k v]] [(name k) (if (number? v) (inc v) v)]) {:a 1, :b {:c 2, :d {:e 3}}}) {"a" 2, "b" {"c" 3, "d" {"e" 4}}})
actual: clojure.lang.ExceptionInfo: Call to #'example.common/walk-map did not conform to spec:
In: [0] val: ([[] []]) fails at: [:args :f] predicate: (apply fn), clojure.lang.PersistentVector cannot be cast to clojure.lang.Named
:clojure.spec/args (#object[example.common_test$fn__21912$fn__21926 0x5ea46e79 "example.common_te[email protected]"] {:a 1, :b {:c 2, :d {:e 3}}})
In the tests that rely on walk-map
:
ERROR in (t-underscore-keys) (core.clj:4631)
expected: (= (sut/underscore-keys {"a-b" 1}) {"a_b" 1})
actual: clojure.lang.ExceptionInfo: Call to #'example.common/walk-map did not conform to spec:
In: [0] val: ([[] []]) fails at: [:args :f] predicate: (apply fn)
:clojure.spec/args (#object[example.common$underscore_keys$fn__20550 0x1f36bed9 "[email protected]"] {"a-b" 1})
These error messages aren't intuitive. I've got a feeling this will be another thing newcomers to Clojure really struggle with.
This looks like it might help with the vague error message: https://github.com/clojure/clojure/commit/68fe71f0153bb6062754442c0a61c075b58fd9bc
It recurses into a ::pcat
to expand out the error explanation, and I think I'm looking at a ::pcat
above.
Okay, so only the commented out test fails:
(deftest t-walk-map
(are [f m x] (= (sut/walk-map f m) x)
identity nil {}
identity {} {}
identity {:a 1} {:a 1}
;; (fn [[k ^long v]] [(name k) (if (number? v) (inc v) v)])
;; {:a 1 :b {:c 2 :d {:e 3}}}
;; {"a" 2 "b" {"c" 3 "d" {"e" 4}}}
))
Okay. Looks like spec was exercising my walk-map
with keys like []
and ()
. I was then calling name
on vectors etc.
Fixed that by making my test fn valid. 🙂
(deftest t-walk-map
(are [f m x] (= (sut/walk-map f m) x)
identity nil {}
identity {} {}
identity {:a 1} {:a 1}
(fn [[k v]]
[(if (named? k) (name k) k)
(if (number? v) (inc ^long v) v)])
{:a 1 :b {:c 2 :d {:e 3}}}
{"a" 2 "b" {"c" 3 "d" {"e" 4}}}))
One down. One to go!
ERROR in (t-underscore-keys) (core.clj:4631)
expected: (= (sut/underscore-keys nil) {})
actual: clojure.lang.ExceptionInfo: Call to #'example.common/walk-map did not conform to spec:
In: [0] val: ([[] []]) fails at: [:args :f] predicate: (apply fn)
:clojure.spec/args (#object[example.common$underscore_keys$fn__20550 0x6dd8e64b "[email protected]"] nil)
It was. Because I had enabled instrumentation my functions were being called with vectors, sets, etc. and those weren't supported.
so i'm converting from schema, and i'm having some issues with respect to preventing clutter within my namespace. maybe i'm not structuring my data correctly
ex. `(def TextBlock {:type (schema/eq "Text") :foreground-color Color :background-color Color :style {(schema/optional-key :bold) schema/Bool (schema/optional-key :underline) schema/Bool (schema/optional-key :italic) schema/Bool} :text Letter}) (s/def ::foreground-color ::color) (s/def ::background-color ::color) (s/def ::style (s/keys :opt-un [::bold ::underline ::italic])) (s/def ::textblock (s/keys :req-un [::type ::foreground-color ::background-color ::style]))`
how would I apply s/def to ::bold, ::underline and ::italic, without s/def? I don't want those in the namespace
if I did have them in the namespace, i'd prefer to have them called something like ::font-style-underline, but the underlying spec would need to accept :underline as the style key
Is there any way to do this? I'm confused on whether I should be inlining the spec stuff. I'm sticking to what I did with plumatic.schema by placing all of my schemas in one file called schemas.cljs
And I'd think you only want really common schema in a global namespace as spec is embracing namespaced keywords.
I've used specs like :common/any-map
because I want nilable maps in a lot of places, but there might be a better way I haven't yet found.
It makes sense keeping everything starting with font
in font.cljs
; that's where I'd look at least!