Fork me on GitHub
#clojure-spec
<
2018-10-22
>
misha00:10:56

also, I feel like you split namespaces too much (with regards to spec, at least.)

hmaurer00:10:56

@misha can you make it much shorter though? you could skip the my-app.schema part, sure

misha00:10:30

the spec kw length you have in example - is ok as is I think, but: I'd used that length for something more descriptive/convenient, so you would not feel the nee to alias it away, and avoid entire issue with dependencies.

misha00:10:58

and when you don't need to use aliases, you just need to make sure all specs are :required at the app entry points. no need to import those all over the place, as those are accessible from global registry, which you can get away initing just once.

misha00:10:53

it is helpful to think about spec-kw namespaces as db table names, not as code namespaces those happen to be defined in. Otherwise specs would not survive first mild refactoring

hmaurer00:10:02

@misha that makes sense; thanks a lot šŸ™‚

hmaurer00:10:42

although I was going to put those spec in a schema namespace, not next to code, so there isnā€™t a big difference there between that and the ā€œtable namesā€ approach youā€™re suggesting

hmaurer00:10:51

beyond the extra verbosity of the prefix

misha00:10:53

you receive map as rest response. it contains "...schema.../..." keys. what a waste of electricity opieop

misha00:10:12

then, imagine, you now need to move them around in the code. ā€“ you either loose all your aliases, or break backward compatibility. ouch

andy.fingerhut00:10:10

I mean, isn't one of the proposals of namespaces in keys that you don't rename them, unless you are planning to make a breaking change in a published API, and you should weigh consequences of that pretty seriously?

hmaurer00:10:11

so I guess it would still be fine to namespace-qualify collection specs (i.e. (s/def ::person (s/keys ...), but you would avoid such long namespace qualification for key specs

andy.fingerhut00:10:55

And gzip and things like Transit reduce the waste of electricity somewhat.

misha00:10:02

there is an ugly workaround, (create-ns 'foo.bar.baz) (alias 'fbb 'foo.bar.baz), but it is meh.

hmaurer00:10:12

because collection specs are essentially a codebase-level notion, whereas keys may be sent over the network, stored, etc

misha00:10:59

@andy.fingerhut imagine "...schema..." suffix in every sql column or table name

andy.fingerhut00:10:28

My mind isn't reeling yet, but I haven't lived and breathed it day in and out, either. Six of one, half dozen of another.

misha00:10:51

and yes, you should not rename those, if they are public outside the app code. therefore if you suddenly need to move definitions elsewhere - file ns and spec ns do not match anymore.

misha00:10:06

@hmaurer nested maps is the collection spec over the wire right there.

hmaurer00:10:24

but the keyword does not appear on the wire

misha00:10:35

anyway. the best way to appreciate this - is to experience pain yourself kappa

misha00:10:53

try to model your app your way, and give it week or two

misha00:10:22

why don't they appear?

{:my-app.schema/persons [{:my-app.schema.person/first-name "joe"}]}

hmaurer00:10:16

ah yep nevermind, youā€™re right

misha00:10:21

also, if you don't preserve ns for js clients - you are missing out as well: "my-app.schema.person/first-name" e.g. cheshire prints qualified keywords this way by default

misha00:10:49

and in js/python/java/xpath/etc. you can just obj["my-app.schema.person/first-name"]

hmaurer00:10:38

so, i.e., in this case, would you name things as:

(ns my-app.schema)

(s/def :schema/person (s/keys :req [:person/first-name :person/friends]))

(s/def :person/first-name string?)
(s/def :person/friends (s/coll-of :schema/person))
?

misha00:10:44

well, if you are confident you will not have external person - it might be ok. otherwise, prepending with a short project codename would not hurt. but I'd think twice before adding anything between codename and entity name

misha00:10:49

if you are in a corporate setting, you are probably required to add at least full org name instead or in addition to project name: :com.cognitect/person :com.cognitect.project/person

borkdude07:10:33

Is it possible to have clojure.spec not throw exceptions on fdef violations, but only print ā€œfdef error, line so and soā€ and then continue as always?

hmaurer13:10:30

is there a way to conform a value to a spec only one level deep?

hmaurer22:10:43

https://github.com/funcool/cats/blob/master/src/cats/monad/either.cljc#L48 Is there anyway I can spec such a type (`Either left right`) on a per-function basis? So for example, I might want to add a spec to a function saying it returns either a string? or a number?, etc

Alex Miller (Clojure team)22:10:23

Always good to remember that specs are not types. You can use s/or to spec an alternative but thatā€™s not going to be generic.

hmaurer22:10:25

(I realise this sounds similar to what the s/or spec does, but I need to work with records here, as defined in cats)

hmaurer22:10:50

is there any way I can do something generic in this case @alexmiller?

Alex Miller (Clojure team)22:10:49

You can treat records as maps with unqualified keys and use :req-un to specify specs on keys

Alex Miller (Clojure team)22:10:06

For specific functions, you could define a :fn spec that checks a relationship between args and ret

hmaurer22:10:28

@alexmiller but then Iā€™ll have to make qualified keys for every possible way there is to use the Either record. As far as I know there is no way to directly define an unqualified map spec, right? i.e. nothing like (s/foo :left string? :right number?) which would accept a map with keys :left and :right matching the specs

misha23:10:51

@hmaurer

(s/valid?
  (s/cat
    :left  (s/tuple #{:left} string?)
    :right (s/tuple #{:right} number?))
  {:left "a" :right 1})
=> true
kappa

Alex Miller (Clojure team)23:10:07

this s/cat assumes maps are ordered, which they are not. in fact, in latest spec, you will get false here.

Alex Miller (Clojure team)23:10:50

s/cat can only be used with sequential? collections now

misha23:10:05

@hmaurer there is also more legal way, where map-of will permit only 1 map-entry:

(s/or
  :left  (s/map-of #{:left} string?)
  :right (s/map-of #{:right} number?))

misha23:10:17

don't do it

misha23:10:30

(s/def :cats/left (s/or :string string? :exception #(instance? Exception %)))
(s/def :cats/right any?)
(s/def :cats/monad (s/keys :req-un [(or :cats/left :cats/right)]))
(s/valid? :cats/monad {:left (ex-info "foo" {})})
(s/valid? :cats/monad {:left "a"})
(s/valid? :cats/monad {:right [:bar]})
    
=> true
=> true
=> true

misha23:10:38

you then can (s/merge) with :more.specific/right

misha23:10:50

but I'd rewrite all using exceptions :D

hmaurer23:10:53

Right, but then for every potential use of Either i need to define a keyword spec, i.e. :some.specific/right

hmaurer23:10:02

same for left

hmaurer23:10:13

instead of being able to just specify it inline in the return spec of a function

misha23:10:23

well, the same for any return value, no?

misha23:10:18

or, if you will have specific spec for wrapped value anyway, you can unpack in custom predicate, and s/and with that

misha23:10:38

but then you'll lose generators for free opieop

misha23:10:56

consider exceptions kappa

misha23:10:53

or, keep lifting separate from business-logic

misha23:10:05

@hmaurer there is also more legal way, where map-of will permit only 1 map-entry:

(s/or
  :left  (s/map-of #{:left} string?)
  :right (s/map-of #{:right} number?))

favila23:10:19

@hmaurer I had this problem continually, where I have a spec key but I need to narrow it further in specific contexts without changing the key name

favila23:10:28

I ended up making a terrible macro, keys+ to do it

favila23:10:47

(s/def :either/either (s/keys ::req-un [:either/left :either/right])) is the basic notion of "either"

favila23:10:22

(s/spec (s/merge :either/either (keys+ :req-un [:either/left :either/right] :conf {:either/left number? :either/right string?}))

favila23:10:06

that would be "this is an :either/either, but some of the keys use a different generator and predicate"

misha23:10:59

(defmacro speceither [left-spec right-spec]
  `(s/or
     :left  (s/map-of #{:left} ~left-spec  :min-count 1)
     :right (s/map-of #{:right} ~right-spec  :min-count 1)))

(s/valid?
  (speceither string? (s/coll-of number?))
  {:right [1 2 3]})
=> true
conformed values are garbage though, but exercise works just fine

hmaurer23:10:30

thanks @favila! Iā€™ll read this up. Does this spec give nice errors / generators / conforming?

favila23:10:19

The only thing you are allowed to do is use :conf to override the normal spec/predicate/generator for that key

favila23:10:52

ā€œOverrideā€ is maybe too strong because the registered spec is checked too

hmaurer23:10:25

Ah right, great. Out of curiosity do you use Either / cats in your codebase @favila? Or did you write this for another use-case?