This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2017-08-31
Channels
- # aleph (38)
- # beginners (91)
- # boot (4)
- # cider (20)
- # clara (11)
- # cljs-dev (4)
- # clojure (179)
- # clojure-greece (1)
- # clojure-italy (16)
- # clojure-portugal (1)
- # clojure-russia (1)
- # clojure-sanfrancisco (1)
- # clojure-spec (183)
- # clojure-uk (50)
- # clojurescript (111)
- # core-async (24)
- # cursive (4)
- # datascript (11)
- # datomic (29)
- # fulcro (120)
- # gorilla (2)
- # jobs (1)
- # keechma (2)
- # keyboards (26)
- # leiningen (4)
- # luminus (7)
- # lumo (15)
- # off-topic (2)
- # onyx (31)
- # parinfer (12)
- # portkey (1)
- # protorepl (1)
- # re-frame (50)
- # reagent (106)
- # remote-jobs (1)
- # ring-swagger (2)
- # rum (10)
- # spacemacs (17)
- # sql (16)
- # test-check (1)
- # yada (2)
I think I'm slowly losing my mind trying to get my function specs to work right.
(s/fdef example-fn
:args string?
:ret string?)
(defn example-fn [s] s)
This does not work if I try stest/check example-fn
. Why? I'm clearly missing something. I've been re-reading the guide for two days, but something's not clicking, and I can't see where why s/fdef's are going wrong.@tjscollins :args
is always a sequence of argument specs so you want (s/cat :s string?)
there.
How does s/cat work? Do the keys have to match the variable names, or are they just used as informational labels in the output of s/explain?
Have a read of this https://clojure.org/guides/spec#_spec_ing_functions -- it has some examples that should help.
Yeah, I've read through that repeatedly. It's just not making sense to me how s/cat and s/alt work.
The keywords in s/cat
don't have to match the argument names but it's common convention that they do (otherwise your error messages will be less than helpful).
So what is actually happening when the args are checked against s/cat? Do the args pass if they match any spec that's "s/cat"ed together? Match every spec?
s/cat
is a "sequence regex" so it matches the sequence of arguments passed into the function
The guide calls it a regex op, but I don't know what that means in this context
So the first pair passed to s/cat has to match the first arg?
And the second pair matches the second arg?
Sort of... consider
(s/fdef clojure.core/declare
:args (s/cat :names (s/* simple-symbol?))
:ret any?)
That matches zero or more simple symbols as an argument sequence and labels that sequence as :names
So in (declare foo bar quux)
, (s/* simple-symbol?)
will match the three symbols and :names
will be (foo bar quux)
It's quite literally "regex" for sequences (instead of strings).
So something like (s/cat :s string? :i int?)
would match both ("1" 1)
and (1 "1")
?
No, it's sequential.
It would only match "1" 1
How familiar are you with regex for strings?
Alright, would (s/cat :s (s/* string?) :i int?)
match both ("1" 1)
and ("1" "1" 1)
?
Yes, I believe it should
Yeah, I'm familiar with string regexes
Okay, that makes more sense now
You can try it out directly in the REPL, without worrying about arguments: (s/conform (s/cat :s (s/* string?) :i int?) ["1" "1" 1])
and (s/conform (s/cat :s (s/* string?) :i int?) [1])
=> {:i 1}
because there were no strings at the beginning
Alright that makes a lot more sense now. Is s/alt basically s/or for a sequence?
That helps a lot. Thanks.
Also from that guide:
(s/cat :prop string? :val (s/alt :s string? :b boolean?))
First element has to be a string, second can be boolean or string, right?
I'm trying to spec a sequence where there is a fixed predicate/spec based on position, but the sequence may omit things at the end; and I'd like it to conform to a flat map, one key per position, nil or no-key for the omitted items
I can't use cat plus nested s/?
because that backtracks (last example would be accepted)
the best I can think of is a linked-list type structure of nested s/or
that a conformer flattens
s/or of s/tuple ?
I’m saying you could s/or multiple s/tuple together
it’s unclear how much variability there is here
I guess maybe s/alt of s/cat would be better for your conformed value
I think I’d say: don’t fight the conformed value. write the right spec, then transform the conformed value to what you want.
always come back to: how can I state the truth about the data?
I essentially want s/tuple, with keyed names for each slot (not numbers), and possible truncation on the right side for optional values
sounds like you really want s/cat with nested s/?
(s/cat :1 int? (s/? (s/cat :2 string? (s/? (s/cat :3 keyword?)))))
etc
the downside of that is the result will be nested maps
oh, I missed the keys in there (always do that)
(s/cat :1 int? :more (s/? (s/cat :2 string? :more (s/? (s/cat :3 keyword?)))))
(s/conform
(s/cat
:a number?
:tail/next (s/? (s/cat
:b string?
:tail/next (s/? (s/cat :c keyword?)))))
[1 "" :k])
I would separate that from the spec, but yes should be able to regularize both the creation of these (with a macro) and the transformation to something you want
@alexmiller When using a predicate as a spec (which can occur in, say, s/assert
), the value of :clojure.spec.alpha/spec
is a function. However, if a proper spec is defined, the :pred
is a symbol. https://gist.github.com/bhb/de31b73133d6322158a3c0a5d47d67a2
(My goal is to provide a consistent printer that can say “<value> did not match <predicate>” in all cases)
I did not see a JIRA ticket, but would be happy to create one if it is helpful 🙂 I’m not sure if there is a technical limitation here.
I suspect this is related to the fact that s/def
is macro whereas explain-data
is a function, so it only gets the function value
i.e. the symbol is already resolved by the time explain
and explain-data
see it. Hm. I wonder if the right solution is to spend time making a pretty-printer for functions that works reliably across CLJ and CLJS
there is a ticket and a patch for that
but your reasoning is correct
https://dev.clojure.org/jira/browse/CLJ-2068 is what I’m thinking of
but maybe it’s not covering what you’re asking
you should not have any expectation about the concrete type of :clojure.spec.alpha/spec, other than that it can be used as a spec
No, you’re correct, this is what I want: I am not concerned about the concrete type, I just want a way to consistently pretty print the problem.
that ticket fixes the :pred in the explain-data
by unmunging the function name
Although in my case, I’d probably print the set differently (instead of a set literal, I’d enumerate options), but that should not be a problem
I’m hoping it will be in the next spec release
Does it apply the same unmunge to :clojure.spec.alpha/spec
at the top level of the explain-data?
user=> (s/explain odd? 10)
val: 10 fails predicate: odd?
(running with the patch)
I have been too busy to look at expound, but hope to soon
Don’t look too closely: the internals are a bit of a mess, but I hope to refactor soonish 🙂 . Most importantly, I want to cleanly separate the pretty printing part (hopefully useful to end users, but not really reusable) from the stuff I did related to adding information to the “in” paths, which could be useful for any library that wants to consume explain-data
seems wise
Can I trust describe to be consistent on specs of the same kind. Like will all s/keys describe itself in a consistent way?
I'm going to build reflective logic on spec descriptions, but I want to be sure its reliable.
Hi! Is there a built-in function to conform a spec and throw an error if the spec does not conform?
My use-case is basically this: I want to pipe data through a series of steps, and I would like to throw an error if a spec does not conform
@hmaurer Just make sure to call (s/check-asserts true)
to turn on assert checking for spec
@bbrinck ok. Is it sensible to use s/assert
when validating user input, e.g. an API payload?
@hmaurer I would prefer to just use explain-str
in that case personally, since a) assert
will raise an exception that you’ll need to catch and b) turning on asserts will enable asserts throughout your program, which you probably don’t want. I’d only use assert
for dev-time checks/invariants
I’m assuming the flow is check data against spec if it works, proceed else, show message to user?
I would like to have some flow like this: check data again spec => validate through authorization filters => assign UUID => transact to database
If your user is a dev at dev-time, I think assert is fine. But if you want to show end-user error messages, I’d avoid it
I am quite new to Clojure; would you in general avoid to throw errors and return error conditions instead? Aka use a “Either”-like type, similar to Haskell/etc?
Good question. I don’t see a lot of use of “either” in Clojure code. Exceptions are fine if it’s actually an exceptional case , but AIUI, in your case, you want to validate user input so you expect things to not conform
You are right. I would like to be able to chain my pipeline steps with (-> ...)
, but breaking out of the pipeline on error seems hard to do without exceptions
I don’t have great advice here for general error patterns, but I’d lean towards “use exceptions for exceptional cases, not control flow”
There are libraries for this, but I don’t know how idiomatic they are. I haven’t used them myself
@hmaurer Some discussion here: https://www.reddit.com/r/Clojure/comments/6wmnfm/thoughts_on_failjure_vs_funcoolcatseither_for/
It's more idiomatic to use exceptions with ex-info. I would wrap explain-str or explain-data in a function which throws an ex-info, where I handle it at the place where my code can return an error msg to the user. There I'd parse out what I need from the exception into the msg I want to show the user and return it.
Thanks! I’ll read this. I like the philosophy of using errors for exceptional cases only though; considered following that approach in a previous javascript projct
What you do is in the ex-info map, you put a key such as :type :validation-error and so where you catch it, you know this isn't a bug, but a validation error. Normally, the exceptions should not log error or push failure metric, as they are actually expected behaviour
Even if you use exceptions in this case (not necessarily bad IMO), I’d still probably throw an exception manually instead of relying on assert
since turning on/off asserts is a global thing. IOW, do you want to enable all asserts in production?
That’s a valid question, but you probably don’t want it tied to your user-facing errors messages
@hmaurer Shameless plug, but if you are considering showing spec error messages to users, take a look at https://github.com/bhb/expound
I'm actually disappointed in spec for improving clojure error msgs. They promote the idea, but it really doesn't help because the error is so long. Its only slightly friendlier then a stack trace
IMO, the default error messages are a good default, since they need to be useful “out of the box” in a variety of contexts: shown to a end user, shown to a dev, logged, returned as an API response, etc. Expound makes a different set of tradeoffs that are very much optimized for the “display to dev” case - *much* more verbose, and omits some information, but hopefully helps devs find the root problem faster
Luckily we can swap out the default for custom printers like expound when we want to customize behavior 😉
Expound is probably still not quite optimized for end users, but I am looking to make it more suitable for this in the future.
I don't know. Id like more beginner friendly defaults in Clojure. Easier to swap out to something else when you're not a beginner anymore. Its a lot to ask to a beginner to learn how to setup expound to figure out why his code doesn't work.
@didibus I agree with you that it’d be nice to get something easier for beginners. I have been wondering if it would be possible to create a lein plugin or library that would set up beginner-friendly REPL / logs by default
Adding a single dependency that loads core specs, turns on instrumentation (with orchestra?) and configures spec to use expound without any further config could be really useful
That would be really nice. Maybe if it can also add a coloured repl and preload clojure.repl
You'd just be surprised how many top class engineers don't bother to learn how to setup any environment properly.
So when I tell them, if you want to do Clojure and like it, you need to learn at least how to setup a working repl. And already I've lost the interest of half of them
@didibus I think @sekao is the best person to talk to about this since he has written three IDEs so far and each one tries to lower the burden of getting everything set up
my favorite is nightmod since I can send someone the download link and they can be in a new project with a repl, starter code, and a live game environment in less than 5 minutes
In clojure.spec.alpha, s/invalid? is defined like (identical? ::invalid ret)
. I thought we couldn't rely on keyword interning anymore?
hi. I have a problem creating a recursive spec. the s/spec
requires the spec to exist:
(require '[clojure.spec.alpha :as s])
(def data
["/api"
["/ping" {}]
["/pong" {}]])
(s/def ::route
(s/cat
:path string?
:arg (s/? (s/map-of keyword? any?))
:childs (s/* (s/spec ::route))))
; CompilerException java.lang.Exception: Unable to resolve spec: :user/route
Setting something temporary as a spec and double-evaluating the original spec seems to work thou, but don’t think this is the way to do this 😉
;; declare'ish
(s/def ::route int?)
(s/valid? ::route data)
; false
;; resetting the spec works now (but the childs is wrong)
(s/def ::route
(s/cat
:path string?
:arg (s/? (s/map-of keyword? any?))
:childs (s/* (s/spec ::route))))
(s/valid? ::route data)
; false
;; now it might work?
(s/def ::route
(s/cat
:path string?
:arg (s/? (s/map-of keyword? any?))
:childs (s/* (s/spec ::route))))
;; ....
(s/valid? ::route data)
; true
@alexmiller My attempt to spec the s/cat s/tuple hybrid with optional trailing positions https://gist.github.com/favila/67c869b3c92b1a40559f639b065734e2
Oh, my recursive spec works when I wrap the child spec into something, like s/and
(s/def ::route
(s/cat
:path string?
:arg (s/? (s/map-of keyword? any?))
:childs (s/* (s/spec (s/and ::route)))))
(s/valid?
::route
["/api"
["/ping" {}]
["/pong" {}]])
; true
Is this the correct way to do this?Hi! Quick question: I have a spec for a map which has required and optional keys (`(s/keys :req [..] :opt [..])`). One of my functions takes “partial” versions of those maps, which essentially means that every key is optional (and every key that was optional in the full spec can be nil
, but I can handle that separatly)
it there a neat way to do this, how should I define two versions of the map specs? (e.g. :foo/my-map
and :foo.partial/my-map
)
hello everyone, im reading the spec docs (`https://clojure.org/guides/spec`)
is it possible to achieve with spec/fdef
something similar to this? (catching spec errors at run time):
(defn person-name
[person]
{:pre [(s/valid? ::person person)]
:post [(s/valid? string? %)]}
(str (::first-name person) " " (::last-name person)))
hello, I'm having an issue trying to define the following specs:
(s/def :user/accessors (s/keys :opt [:user/id :user/name :user/address]))
(s/def :user/id string?)
(s/def :user/name string?)
(s/def :user/address :address/accessors) ; <- trouble maker
(s/def :address/accessors (s/keys :opt [:address/street :address/user]))
(s/def :address/street string?)
(s/def :address/user :user/accessors)
this is a simplified case where I need to set a spec to something that still are going to be defined
right now this throws an error, as:
CompilerException java.lang.Exception: Unable to resolve spec: :address/accessors, compiling:(2017.clj:10:1)
I remember reading that the definitions should be lazy to allow this kind of things, am I missing something here?
I always known it as not being lazy. Can't you just reorder your definitions? Being lazy would make it risky I feel, what if specs are accross namespaced and lazy, and you forgot to load another namespace.
@hmaurer you can define multiple versions, create a simpler one and then you can merge it with other to create a more fully-featured, example:
(s/def :foo.partial/my-map (s/keys :req [:foo/a :foo/b]))
(s/def :foo/my-map (s/merge :foo.partial/my-map (s/keys :req [:foo/c])))