This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2019-09-05
Channels
- # announcements (7)
- # beginners (107)
- # boot (5)
- # calva (2)
- # cider (18)
- # clj-kondo (48)
- # cljs-dev (16)
- # cljsrn (2)
- # clojure (208)
- # clojure-berlin (1)
- # clojure-dev (25)
- # clojure-europe (14)
- # clojure-italy (10)
- # clojure-nl (10)
- # clojure-sg (1)
- # clojure-spec (52)
- # clojure-uk (13)
- # clojurescript (53)
- # cursive (7)
- # data-science (7)
- # datomic (4)
- # duct (1)
- # events (10)
- # fulcro (1)
- # graphql (5)
- # jobs (2)
- # kaocha (13)
- # leiningen (6)
- # off-topic (17)
- # pathom (4)
- # quil (6)
- # re-frame (52)
- # reagent (12)
- # reitit (3)
- # shadow-cljs (97)
- # spacemacs (10)
- # sql (39)
- # tools-deps (18)
- # uncomplicate (1)
- # xtdb (1)
I have a spec which reuses another, something like
(s/def ::a
(s/and #(s/valid? ::sub-a %)
(s/keys :req [:a/only-attribute]))
However, my problem is that s/explain
does not show the ::sub-a
spec violations, it just says that ::sub-a
is invalid
is there a way for spec to “walk” down ::sub-a
in this case?
I could, of course, copy over the (s/keys)
in ::sub-a
to the spec of ::a
but I was looking for a solution that reuses the ::a
declaration
Right now, this is what I would recommend. Much better options coming for this in spec 2
I actually got good error messages by doing
(s/def ::a
::sub-a
(s/keys :req [:a/only-attribute]))
It this bad @U064X3EF3?
That looks invalid, what are you seeing?
I’m actually getting the error messages for ::sub-a
should that not work?
@U064X3EF3 got inspiration from https://gist.github.com/robert-stuttaford/e1bc7bfbcdf62020277dda1a277394ca
how can i describe a map where a key could either contain a normal value ::some-object
or, in an error-case, an error-value?
I don't want to add the error-case to ::some-object
as that would greatly decrease the expressiveness and reliability,...
Spec is about expressing the true range of values. If it can contain special error values, then that is part of that truth
If that’s weird, you might consider using an alternate attribute for the error or using exceptions
exceptions are an extremely ugly solution (at least for me, coming from ADT Either-type land)... and my data cannot contain errors, but the place they're transmitted can. i guess i'll create a wrapper object that can be either of type success or of type failure, and then contain the values respectively
Exceptions are the idiomatic solution on the jvm and in Clojure
i guess thats an idiom i dislike ^^ i really hate the complexity that braking out of my control flow adds, especially when otherwise working with pure functions that are easy to reason about ;D and in my case exceptions aren't really a simple option, as im working in clojurescript with re-frame, where im dispatching events and then dispatching other events on success
Problem is: if you go down the path of “exceptions are bad,” you’re fighting not just the clojure ecosystem, but the Java(Script) one. Leveraging host libs is one of the primary reasons clojure(script) exists at all!
Not to say you can’t write clj(s) w/o host libs. Many try. But know that you’re giving up a primary value-add for the lang.
Many have also tried to improve upon exceptions (e.g. slingshot). I’ve found them to be marginal gains at best. Not worth their overhead.
yea i know, i don't have any direct issue with exceptions, i just really hate using them where not necessary. and as i said, in my case, they are pretty much not a solution, as the one observing the result isn't the same thing that calls the thing that could throw. for such cases, where error-data makes more sense than exceptions, some solution would really be great. but with the current a spec only talks about the keys it gets, the type of the values HAVE to be defined as part of that key really makes something like this nearly impossible, and strongly increases the complexity in knowing what shape your data actually has, because i CANNOT actually spec it without introducing additional complexity (and removing a hell of a lot of expressivity) to my domain model
if your data can contain error values, then spec'ing it means spec'ing those values. spec is just exposing that complexity that you have introduced
my data cannot contain errors. a book
doesn't contain any error. a find-book-response
can contain that error. the big problem is that spec forces me to define find-book-response
specs for everything, and by that introduces an additional layer of complexity where something like generics could really help.
the whole notion of needing to have a spec for a key in a map makes many things really complicated for no good reason.
say you have a utility function, that takes a (s/keys*)
map as arguments (maybe it does some complex math where you want the arguments to be named for readability.
now you need to create spec defs for each int?
that the function can take, thus adding extreme amounts of complexity where a
(s/kv :req {:num1 int?, :num2 float?})
would solve your problem perfectly.
enforcing best practices is a good thing, don't get me wrong. but if these best practices only apply in some areas, but you enforce them in every little situation, that really screws up what could have been an incredibly powerful and nice solution to safety and type-validation in a fully dynamic environment
I don't understand what's forcing you to define anything
keys specs are open - you don't have to spec every attribute
say you have some complex, user-input based form, where the data doesn't really mean anything specific from a domain-model point of view. if you want to validate that data in a map context, you'll need to define seperate, single use specs for each entry in that map that you want to validate.
I'd say that "just don't spec things that would take to much effort" isn't a desirable solution.
not only does your safety suffer from that, but it also defeats the purpose of spec in general.
because then you could just say "just manually write (if (not (int? my-number)) (throw Exception. "foo"))
,... thats not the point
well yes, but not for every purpose
given that these are not for your particular domain, but a truly dynamic problem, you're a bit off the goals of spec
you certainly could use a pairing of fields and specs to generically validate (but don't validate the aggregate map)
then no registration is needed
still, what is the actual reason there is no spec-function that takes keys ( be they namespace qualified or not) and their types (predicates, etc) and validates a map against them? is it just to encourage good practises?
I don't know that I explain it better than Rich, who has written/talked about this extensively
the whole idea is to imbue semantics to attributes, and register those for use globally
and to de-emphasize the role of the aggregate
that is, the attributes are the driver, not the aggregation (the map)
this is taken much further in the schema/select work in spec 2 (and s/keys is going to go away)
spec 2 schemas do have inline support for ad hoc un-namespaced key specs
https://github.com/clojure/spec-alpha2/wiki/Schema-and-select#unqualified-keys
the ability to programatically create and use specs is greatly enhanced in spec 2 as well
so spec 2 will support that kind of thing i'm requesting? as i said, i fully understand the "attributes are the driver, not the aggregation" argument, and i think as a base philosophy it's actually rather genius. but there still are a lot of cases where these attribute-semantics are single-use function arguments. and in these cases, having to give names to things that only make sense in a very specific context is increasing complexity and hurting readability. the "attributes are the driver" argument stops working as soon as you leave your direct domain-model and go into functions that achieve very specific purposes with specific parts of that data. having to describe the shape of every little thing there really makes this a lot more complicated. especially in my current project, a clojurescript re-frame based frontend application, i really want to spec out every view function. not strictly for validation or domain-expressivity, but actually primarily just to get any kind of good errors. knowing that a view function got its arguments in the wrong shape/type is a lot more helpful than knowing that some reagent-internal had problems.... for these purposes spec would nearly be perfect. dynamic validation, that is actually close to dependent type-systems, would be the perfect solution to greatly specify what each function expects as arguments, no matter how detached that function is from the domain model. but the way that spec currently prohibits the generation of map-specs inline makes a lot of things hugely more complex. having a view function that takes 4 arguments look like this:
(s/fdef foo
:args (s/cat :title string?, :subtitle string?, :rating ::domain/rating, :content string?))
(defn foo [title subtitle rating content]
...)
is great for the spec. but calling the function gets anoyying and unreadable, so you want to add keyworded args:
(s/def ::title string?)
(s/def ::subtitle string?)
(s/def ::content string?)
(s/fdef foo
:args (s/cat :props (s/keys* :req-un [::title ::subtitle ::domain/rating ::content])))
(defn foo [& {:keys [title subtitle rating content]}]
...)
this is easier to call and read from caller side.... but extremely unnecessarily complex from implementation side.
(yes, title subtitle and content could be domain specific stuff, but maybe im writing a component library that doesn't HAVE any domain specific information.)
do you see where i come from?those seem almost the same. so not sure I'm getting "extremely unnecessarily complex"
just the extra s/defs?
I don't think spec 2, at this exact moment, helps in this particular situation. however, it's not really easy to judge as we are currently completely reworking how function specs are defined and used and it's probably not going to bear much similarity to spec 1
something like (s/schema {:title string? :subtitle string? :rating int? :content string?}) is sufficient at current moment to spec a map with those (unqualified) keys. can't currently apply that ala s/keys* - that's still a tbd right now (I don't think it's a big deal to address, just not current focus)
In this case it's not that Bad, but in more complex cases, and especially all over the Code, it gets very anoyying to type and Deal with. It would be great if that could be Adressed in spec2, as it then would be useful in pretty much any Situation without being overly verbose
Sorry, I do not have much constructive to say, but I do recall an explicit statement made by one of the Clojure core team (probably Stu Halloway) that something can be simple (i.e. not complex) but still be verbose.
yes, thats the java philosophy. "make simple things seem 5 times as complex by introducing unnecessary abstractions, syntactical overhead and overengineered design patterns". that its POSSIBLE to make something simple overly verbose isn't a good thing, and absolutely nothing you should WANT