This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2018-03-22
Channels
- # beginners (240)
- # boot (23)
- # bristol-clojurians (3)
- # cider (101)
- # cljs-dev (52)
- # cljsrn (17)
- # clojure (212)
- # clojure-dusseldorf (2)
- # clojure-greece (2)
- # clojure-italy (9)
- # clojure-russia (1)
- # clojure-spec (91)
- # clojure-uk (33)
- # clojurescript (164)
- # community-development (23)
- # core-async (24)
- # core-logic (9)
- # cursive (18)
- # datavis (1)
- # datomic (119)
- # emacs (13)
- # events (1)
- # figwheel (2)
- # fulcro (86)
- # graphql (1)
- # immutant (5)
- # jobs-discuss (6)
- # leiningen (19)
- # lumo (46)
- # nyc (7)
- # off-topic (23)
- # parinfer (15)
- # pedestal (3)
- # planck (32)
- # re-frame (48)
- # reagent (75)
- # ring-swagger (13)
- # rum (32)
- # shadow-cljs (402)
- # spacemacs (5)
- # specter (3)
- # tools-deps (11)
- # unrepl (20)
- # vim (135)
- # yada (3)
So Stuart Halloway gave a talk on Clojure spec at Strange Loop 2016 that I am transcribing now (mostly done), where he said this: 'So one of the things that is fun about developing with spec is: as soon as you have made a spec, and it can even be a very rudimentary one, you can then say "give me example data" or "give me example functions that do this".'
If you want to see it in context of the full text, you can search for that quote here: https://github.com/jafingerhut/talk-transcripts/blob/add-halloway-agility-robustness-clojure-spec-transcript/Halloway_Stuart/AgilityRobustnessClojureSpec.md
The part that I am wondering how to do is the part where he says "give me example functions that do this". Does anyone know if that is possible, and if so, has examples of doing that?
He was probably referring to the stub capabilities of instrument
fspec
s can be generated I think
(def my-fn (-> (s/fspec :args (s/cat :x int?) :ret string?)
(s/gen)
(gen/sample 1)
(first)))
(my-fn 1)
=> "8VRbFJ59e2K2Ic79F8CX8HB1"
(my-fn 1)
=> "WSKN70JT5kZG"
is it just making a function that ignores its inputs and using the :ret
spec to generate return values?
> fspecs can generate functions that validate the arguments and fabricate a return value compliant with the :ret spec, ignoring the :fn spec if present.
yes the return is unrelated to the input I think
I made a ticket many decades ago to discuss whether it should at least be a pure function
it's also currently independent of the test.check seed I think
I think this is a really dumb question, but how can I detect which branches were taken/captured in a s/cat
?
doesn't the conformed value tell you that?
yeah I can see it in the data, I was just wondering if there was a function you could call to get back which parts matched
the context is that I'm trying to parse defn
forms and reassemble them with tracing wrapped around parts of the function
and the function specs have quite nested data that is a little bit annoying to pull out
So I end up with this disgusting code:
(defmacro fn-traced
[& definition]
(let [conformed (s/conform ::ms/fn-args definition)
name (:name conformed)
bs (:bs conformed)
arity-1? (= (nth bs 0) :arity-1)
args+body (nth bs 1)]
(if arity-1?
(if (= :body (nth (:body args+body) 0))
(if name
`(fn ~name ~(:args (:args args+body))
~@(map (fn [body] `(dbgn ~body)) (nth (:body args+body) 1)))
`(fn ~(:args (:args args+body))
~@(map (fn [body] `(dbgn ~body)) (nth (:body args+body) 1))))
;; else :prepost+body
(if name
`(fn ~name ~(:args (:args args+body)) (:prepost (nth (:body ~args+body) 1))
~@(map (fn [body] `(dbgn ~body)) (:body (nth (:body args+body) 1))))
`(fn ~(:args (:args args+body)) (:prepost (nth (:body ~args+body) 1))
~@(map (fn [body] `(dbgn ~body)) (:body (nth (:body args+body) 1)))))
)
)
was wondering if there's a pattern for doing this in a cleaner way
small critique, seems like the if name
bits could be done inside the (fn ...
part to avoid repeating the whole fn expression
but if there is no name then I'll end up with (fn nil [] ...)
Am I possibly approaching this whole thing in the wrong direction?
Feels pretty dirty
@alexmiller anything missing for https://dev.clojure.org/jira/browse/CLJ-2311 ?
I'll be able to clean this up a lot, but here's how it turned out:
(defmacro fn-traced
[& definition]
(let [conformed (s/conform ::ms/fn-args definition)
name (:name conformed)
bs (:bs conformed)
arity-1? (= (nth bs 0) :arity-1)
args+body (nth bs 1)]
(if arity-1?
(if (= :body (nth (:body args+body) 0))
(if name
`(fn ~name ~(:args (:args args+body))
~@(map (fn [body] `(dbgn ~body)) (nth (:body args+body) 1)))
`(fn ~(:args (:args args+body))
~@(map (fn [body] `(dbgn ~body)) (nth (:body args+body) 1))))
;; else :prepost+body
(if name
`(fn ~name ~(:args (:args args+body)) ~(:prepost (nth (:body args+body) 1))
~@(map (fn [body] `(dbgn ~body)) (:body (nth (:body args+body) 1))))
`(fn ~(:args (:args args+body)) ~(:prepost (nth (:body args+body) 1))
~@(map (fn [body] `(dbgn ~body)) (:body (nth (:body args+body) 1))))))
;; arity-n
(let [bodies (:bodies args+body)]
(if name
`(fn ~name
~@(map (fn [a+b]
(println "A+B" (:body a+b))
(if (= :body (nth (:body a+b) 0))
`(~(:args (:args a+b))
~@(map (fn [body] `(dbgn ~body)) (nth (:body a+b) 1)))
;; prepost+body
`(~(:args (:args a+b))
~(:prepost (nth (:body a+b) 1))
~@(map (fn [body] `(dbgn ~body)) (:body (nth (:body a+b) 1))))
))
bodies))
`(fn ~@(map (fn [a+b]
(println "A+B" (:body a+b))
(if (= :body (nth (:body a+b) 0))
`(~(:args (:args a+b))
~@(map (fn [body] `(dbgn ~body)) (nth (:body a+b) 1)))
;; prepost+body
`(~(:args (:args a+b))
~(:prepost (nth (:body a+b) 1))
~@(map (fn [body] `(dbgn ~body)) (:body (nth (:body a+b) 1))))
))
bodies)))))))
@mpenet here's how to not repeat the name section twice:
`(fn ~@(when name [name])
~@(map (fn [a+b]
(if (= :body (nth (:body a+b) 0))
`(~(:args (:args a+b))
~@(map (fn [body] `(dbgn ~body)) (nth (:body a+b) 1)))
;; prepost+body
`(~(:args (:args a+b))
~(:prepost (nth (:body a+b) 1))
~@(map (fn [body] `(dbgn ~body)) (:body (nth (:body a+b) 1))))
))
bodies))
Wrap a single element in a collection and then unsplice it
what would be the most idiomatic way of expressing xor for two keys in a map?
it is possible to actually define xor and use it in :req, but I do not guarantee that that will work forever
separately you can s/and around the map to enforce a predicate
That's a bit better:
(defn fn-body [args+body]
(if (= :body (nth (:body args+body) 0))
`(~(or (:args (:args args+body)) [])
~@(map (fn [body] `(dbgn ~body)) (nth (:body args+body) 1)))
;; prepost+body
`(~(or (:args (:args args+body)) [])
~(:prepost (nth (:body args+body) 1))
~@(map (fn [body] `(dbgn ~body)) (:body (nth (:body args+body) 1))))))
(defmacro fn-traced
[& definition]
(let [conformed (s/conform ::ms/fn-args definition)
name (:name conformed)
bs (:bs conformed)
arity-1? (= (nth bs 0) :arity-1)
args+body (nth bs 1)]
(if arity-1?
`(fn ~@(when name [name])
~@(fn-body args+body))
;; arity-n
(let [bodies (:bodies args+body)]
`(fn ~@(when name [name])
~@(map fn-body bodies))))))
Apologies if this doesn’t work in your case, but would this approach work? We’ve used it successfully to insert timers around function bodies http://blog.klipse.tech/clojure/2016/10/10/defn-args.html
The trick is to modify the conformed value and then call unform to go back to valid clojure code
Even if I deleted the docstrings, examples, and workaround for the spec bug, I think my code would longer than your solution, but here’s an example of the approach described in that link https://gist.github.com/bhb/128bf97619e83541a8adda7094bc370d
Thanks, that’s a very clever way to do it
By mistake, but it surprised me:
(s/def :dropdowns/options (s/coll-of :dropdowns/options))
(s/valid? :dropdowns/options []) ;; true, ok, I can get that
(s/valid? :dropdowns/options [[]]) ;; true, uuh...
coll-of doesn’t limit cardinality by default
0 size is valid
Use :count, :min-count etc if needed
I missed that too, but then I was surprised about this behavior, because spec will allow arbitrary nesting of empty colls this way
the way to justify: an empty coll is a valid :foo
, a collection of empy collections is a collection of valid :foo
, hence an arbitrarily nested empty coll is a valid :foo
spec cannot go that far. same problem with (s/def ::first-name string?) (s/def ::last-name string?)
using ::first-name instead of ::last-name in an fdef args signature will be silent, in haskell it would scream at you
How would you write this:
(s/def :dropdown/option (s/keys :req-un [:dropdown-option/id
:dropdown-option/label]))
dropdown-option/id
or dropdown/option-id
. You can only namespace one level>dropdown-option/id Says to me, thats the id of the dropdown option. >dropdown/option-id Says to me, thats the option-id of the dropdown
Is it possible that both s/conform
and s/valid
are failing, but s/explain
returns Success!
?
Not doing anything fancy. Clojure 1.9.0
ok, nevermind, forgot to reload ns
any case of that is a bug