Clojurians
#clojure-spec
<
2017-02-22
>

This page is not created by, affiliated with, or supported by Slack Technologies, Inc.

ag00:02:19

let’s say balance-update-generator for the sake of exercise generates vector of ints

ag00:02:55

hold on. gen-balance-update generates data based on the information of each individual item in the original vector i.e.: (gen-balance-update m)

gfredericks00:02:43

k, modifying...

ag00:02:43

it returns a vector, that vector should go to m under the :bal key

ag00:02:10

I think this is what I need. I’m not sure yet

ag00:02:13

let me try

gfredericks00:02:31

an equivalent approach would be to generate the whole map from gen-balance-update, and then avoid doing the map and assoc on line 15

ag00:02:38

Gary, you are my hero!

ag00:02:17

btw, has anyone encountered that - very often when I run (gen/generate) in clojurescript it just kills the websocket repl

gfredericks00:02:14

is it possible it's generating something Very Big?

ag00:02:09

not too big though…

ag00:02:40

still must be doing something wrong

gfredericks00:02:01

there are certain combinations of generators that have unfortunate distributions where they are Not Too Big most of the time but occasionally Very Big

gfredericks00:02:18

but I don't know anything about the cljs repl so I don't know if that's even a reasonable guess

ag00:02:38

it’s ok, not critical, at least right now for me. I have specs in .cljc files and that’s pretty nice

ag00:02:59

I think it should work fine with tests running in headless browser

ag00:02:31

but in general I try to avoid generating large items, clojurescript struggles

gfredericks00:02:37

I would like it to be difficult to accidentally generate Very Large things, but unfortunately I don't think there's a good general solution for the existing pitfalls

mpenet07:02:23

@alexmiller Should I bring up my case earlier in a jira ticket? or is it something that should be discussed with Rich first. Happy to provide a patch if there's interest.

ikitommi13:02:51

Starting to be quite happy with my custom 3-arity conformer: easy to selectively conform on runtime (json, string, fail-on-extra-keys, drop-extra-keys etc). Is this something that could go into clojure.spec itself? ping @alexmiller. If so, should I just write an issue/proposal of this into Jira? And the idea is allow separating “what” (the spec) from “how” (the conformer). e.g.

(s/def ::name string?)
(s/def ::gender keyword?)

(conform 
  (s/keys :req-un [::name ::gender]) 
  {:name “Tommi”, :gender “male”, :weight 92} 
  (merge json-conformers strip-extra-keys-conformer))
; => {:name “Tommi”, :gender :male}
Requires some spec inspection, but the http://dev.clojure.org/jira/browse/CLJ-2112 should make it hack-free. Spec as Records would help even more here (could just add a custom new Protocol for this).

mpenet13:02:02

clj-2112 could exist as a library until it's finalized

mpenet13:02:41

as long as the final thing doesn't end up too far from what's in the ticket it'd be a decent solution

mpenet13:02:55

parsing clj forms is a bit gross otherwise imho

mpenet13:02:31

(there's also https://github.com/uswitch/speculate doing that, monkey patching spec along the way to reach its goals)

mpenet13:02:53

a bit too convoluted to my taste

ikitommi13:02:08

we have similar patching around.

ikitommi13:02:29

while waiting for things to resolve in the clojure.spec side.

ikitommi13:02:13

and 2112 could be a separate lib, but I would still need the 3-arity confrom for the specs. currently need to wrap all specs to make it work… https://github.com/metosin/spec-tools

ikitommi13:02:19

(and the dynamic binding hack)

mpenet13:02:23

that's the one thing that I don't like so far with spec-tools

ikitommi13:02:45

well that was my question about getting help from the spec, would like to dissolve the hacks on our side.

mpenet13:02:49

I'd rather have the conformers as an arg at another level (spec maybe).

ikitommi13:02:21

me too, the 15:33 comment :slightly_smiling_face:

mpenet13:02:27

understood

mpenet13:02:38

:slightly_smiling_face:

mpenet13:02:30

that said I am dying for swagger/spec in compojure-api

alexmiller13:02:59

@mpenet ticket would be fine - important thing is a good problem statement

mpenet13:02:12

@alexmiller I'll try

alexmiller13:02:15

@ikitommi I don't understand what you're proposing

ikitommi13:02:12

a 3-arity conform that would a third argument, a callback which the Spec’s conform would call with the sepc and the value as arguments, the callback would return either a new conformer function of nil. If it returned, it would be used instead of the installed normal conformer.

alexmiller13:02:50

It seems unlikely Rich would want to add that

alexmiller13:02:36

But starting with a good problem is the way to win his heart :)

alexmiller13:02:48

This is a solution, not a problem

ikitommi13:02:34

If I just write the problem statement into Jira?

alexmiller13:02:01

Yes, that's ok, although I will close it if I don't think it's a problem we are trying to solve

ikitommi14:02:36

ok, will do. thanks.

alexmiller14:02:22

I’ll take a look, thx

mpenet14:02:53

the proposal "only" covers the need to have more info for failures the rest is probably too specific to our stuff (even tho the example mentions it)

tbaldridge14:02:52

@mpenet it's unclear from the ticket, are you parsing strings with spec?

mpenet14:02:34

I want conform/valid to tell me if a spec value is a valid string representation of a "Query"

mpenet14:02:00

so it does parse under the hood, and right now (with our patch) conform returns the parsed AST

mpenet14:02:10

but this is a detail of our use case I think

mpenet14:02:19

the important bit is about the need to preserve the metadata from the failure to extend what's returned by explain* later

tbaldridge14:02:57

Yeah, that'd be nice, imo. But it also seems that passing a parse tree into spec would get your around the problem.

tbaldridge14:02:17

treating Spec like a parser, is a good way to get yourself into trouble, in my experience.

mpenet14:02:25

I am not using spec as a parser at all

mpenet14:02:02

the "parse" function returns either a valid ast or an ex-info, the ast is not consumed by spec in any way ( it's basically a (conformer #(try (parse s) (catch ExceptionInfo :clojure.spec/invalid)))

mpenet14:02:46

the goal ultimately is to have our schemas (that contains these query string representations + tons of other stuff), validated by spec

mpenet14:02:23

and have error reporting/handling at a single level

mpenet14:02:34

otherwise yes, we could do another pass just for query validation

mpenet14:02:49

(outside of spec)

tjtolton16:02:10

Okay, finally managing to get my company to use spec! great stuff! Here's one thing we're stuggling to internalize - our public API uses json that isn't namespaced, and our internal spec's, obviously, use namespaced keys. Is there some way to say "this incoming data is of the same structure as the spec, but the keys are unqualified, so handle all the keys as though they are qualified to this namespace"?

tjtolton16:02:44

or does one have to generate a spec-specific view of the data before passing it into a spec'd function -- essentially

(clojure.core.walk/prewalk #(keyword *desired-ns* %) data)

tjtolton16:02:26

furthermore, I'm struggling to get my company to accept namespaced keys. They essentially view namespaced keys as an implementation detail of spec that should not be complected with our data model or public API. I think the big idea with spec is that namespacing keys is actually just a different way to represent data which spec has chosen to support and encourage. But without having enough experience using namespaces and qualified data, I'm pretty sympathetic to the "don't mix implementation details with data" argument.

not-raspberry16:02:11

If I understand correctly. I only tried to answer the first part of the question.

tjtolton16:02:30

yeah, I think that's outstanding.

frerom18:02:48

Hi, I have a small problem with spec that I would like to get some thoughts on. So the problem is spec name conflicts. Since s/keys couples key and spec names together conflicts can arise:

;; Pet
(s/def ::name string?) ;; CONFLICT
(s/def ::pet (s/keys :req-un [::name]))

;; Person
(s/def ::last string?)
(s/def ::first string?)                 
(s/def ::name (s/keys :req-un [::first ::last]))  ;; CONFLICT
(s/def ::person (s/keys :req-un [::name ::animal]))

(s/valid? ::person {:name {:first "John"
                           :last  "Smith"}
                    :pet  {:name "Buddy"}})
How would your handle this without: - Separating these into different files - Combining the specs, e.g. with s/or

not-raspberry18:02:04

(s/def : string?)

joshjones18:02:09

Well, you can either use two different files, each having its own namespace, in which case ::name will resolve to the namespace it's in, or, you can skip the auto-resolve :: and just name them differently, in the same file:

(s/def :some.namespace/name string?)
(s/def :other.namespace/name number?)
@frerom

frerom18:02:52

I would love to do the above. But s/def only accepts qualified keywords no?

tbaldridge18:02:36

@frerom sure, but you can do: (s/def :person/name ...) (s/def :pet/name ...)

frerom18:02:33

But s/keys only accept qualified keywords

joshjones18:02:59

(s/def :person/name string?)
(s/def ::yourmap (s/keys :req-un [:person/name]))

joshjones18:02:20

(s/valid? ::yourmap {:name "Fred"}) ;; true

frerom18:02:41

Huh. So this is exactly what I want, but it doesn't seem to work in ClojureScript? All I get is

Assert failed: all keys must be namespace-qualified keywords

tbaldridge18:02:10

can you copy your code?

tbaldridge18:02:26

:person/name is a qualified keyword, I think you might have a typeo

frerom19:02:33

Sorry about that, it was a typo. It works perfectly fine. Thanks a lot for the help :slightly_smiling_face:

frerom19:02:17

I guess the risk with this solution is that because the spec doesn't have the namespace in the qualified keyword you might actually get spec name conflicts for different reasons? If :person/name is defined in difference files. So I should probably keep that in mind and not be too generic with the naming.

not-raspberry19:02:54

@joshjones Doing (s/def :person/name string?) is a good idea until someone does the same.

frerom19:02:04

Yes, what I would want to do (If I can make up syntax) is something like ::person/name which expands to :my.namespace.person/name

joshjones19:02:23

(create-ns 'my.namespace.person)
(alias 'person 'my.namespace.person)
(s/def ::person/name string?)
(s/valid? ::person/name "fred") ;;true
::person/name
=> :my.namespace.person/name

joshjones19:02:10

@not-raspberry I agree, although nothing prevents anyone from naming any spec anything they want, including any namespace, so it's just making a potential problem less likely, not eliminating it, and assumes good behavior

jasonjckn19:02:22

is there a way to create an s/keys spec from data? e.g. (s/keys :req-un value)

tbaldridge19:02:00

@frerom right, the names of specs should be bounded to the problem at hand. So if I'm writing a compiler I may use :expr.if/condition while if I was writing a public facing api for a company I may use :com.mycomp.department.person/name

tbaldridge19:02:51

@jasonjckn macros or eval are your options, in that order normally.

jasonjckn19:02:25

alright, i guess macros it is

jasonjckn19:02:41

i have never used eval in 7 years of clojure

frerom19:02:17

@tbaldridge I agree

zane20:02:34

Did you ever get an answer to this question, @tjtolton?

tjtolton20:02:40

:req-un is actually the answer, yeah

tjtolton20:02:51

thanks for following up

ddellacosta23:02:27

what’s the idiomatic way to write something that is essentially an enum of keywords?

ddellacosta23:02:14

I was looking for something like (s/enum :foo :bar) but nothing like that exists; should I simply use (s/or :foo #(= :foo %) :bar #(= :bar %)) ?

ddellacosta23:02:21

seems overly verbose, to say the least

zane23:02:50

@ddellacosta: #{:foo :bar :baz …}

ddellacosta23:02:00

@zane :smile: I feel dumb

zane23:02:08

No worries!