Fork me on GitHub
#clojure-spec
<
2018-03-15
>
gfredericks00:03:55

I don't think you could instrument it (which I assume is the point) at runtime, since it's normally inlined

gfredericks00:03:33

I'm curious how easily compilation could be modified to avoid the inlining does cljs spec do runtime instrumentation at all? I don't even know that

didibus05:03:57

How does one go about adding functionality to spec? I tried to extend s/keys so that it also did a contains check, basically making it a closed-key spec. I did so by wrapping s/keys in a separate macro which takes the same input then s/keys and returns an anded spec of keys and one which will validate that no extra keys are present. But composing macros like that isn't great. And so now I'm trying to extend it even more, so that my closed-keys macro can be used in a s/merge style. Anyways, bottom line is, the experiment has been tedious. So I was thinking, maybe instead of building on top of s/keys, I can just build a whole new spec, but how do you do that?

didibus05:03:04

I also found it frustrated all the specs were macros, which some of them were plain old functions that took clojure data. Like s/keys could have taken a map, or a vector of vectors, instead of a custom macro DSL.

ikitommi05:03:27

@didibus current version of spec doesn’t really support extensions. But you can reify clojure.spec.alpha/Spec and copy-paste current spec internals to make something similar.

ikitommi06:03:50

I think s/keys could be rewritten as a function, as all keys need to be referenced via keyword. Something like: https://github.com/metosin/spec-tools/blob/master/src/spec_tools/data_spec.cljc#L39-L68.

ikitommi06:03:49

.. actually the closed keys would be easy to implement on top of that via extra :closed? key or similar. hmm.

guy13:03:00

If you were writing an fdef for a simple function that takes a collection of strings (at least one) could you use (s/+ string?) instead of (s/coll-of string?) for the :args.

guy13:03:15

(s/fdef my-fn
  :args (s/cat :item (s/+ string?))
  
  or 
  
  :args (s/cat :item (s/coll-of string?))
  ..)

madstap15:03:04

Those are different though, (s/cat :item (s/spec (s/+ string?))) works the same as the latter.

guy16:03:02

@U0J9LVB6G what was the difference out of interest? What does s/spec do to it?

madstap16:03:10

s/spec makes it nested

madstap16:03:59

(s/conform (s/cat :item (s/+ string?)) ["a" "b"])

(s/conform (s/cat :item (s/spec (s/+ string?))) [["a" "b"]])

(s/conform (s/cat :item (s/coll-of string?)) [["a" "b"]])

guy16:03:25

ahhhhh right got it thanks

Alex Miller (Clojure team)14:03:49

I would even say that’s preferred (although you won’t really see much practical difference)

guy14:03:37

ok great thanks

bbrinck14:03:06

@guy keep in mind coll-of also has :min-count arg which might be useful here

bbrinck14:03:12

(although more verbose than +)

guy14:03:34

ah yes thats right thank you!

Alex Miller (Clojure team)14:03:12

generally when spec’ing function signatures, you’re spec’ing syntax, and spec regex ops are the best match so I usually try to stick to those in fdef args

guy14:03:58

ok so in general favour spec regex ops then?

Alex Miller (Clojure team)14:03:50

when spec’ing function args, favor regex op specs

Alex Miller (Clojure team)14:03:09

when spec’ing other kinds of data, I favor collection specs

guy14:03:20

Can you give me an example of the latter sorry

guy14:03:44

Do you mean if you were creating a spec for some sample data perhaps?

guy14:03:18

thanks for the help!

borkdude15:03:53

I have a spec:

(s/keys :req-un [::title
                   ::content]
          :opt-un [::icon
                   ::widget-class
                   ::settings
                   ::collapsed?
                   ::dropdowns
                   ::controls
                   ::preview?
                   ::tabs
                   ::collapse-opts
                   ::help])
Now I want to have a warning for myself when someone passes something that has more keys than I expected. Can I use my spec for this or do I have to duplicate the keys?

borkdude15:03:26

I’m refactoring this component, so other arguments are suspicious

seancorfield15:03:49

@borkdude You can extract the list of keys from the spec form itself, and write a predicate that checks those are the only keys. Stu Halloway posted an example on the mailing list a while back I think... Probably in a Gist somewhere...

seancorfield15:03:40

@taylor Ah, yes. I misremembered. Stu's code was to find any keys in a spec that did not have definitions.

borkdude15:03:02

Hey @taylor, great answers on Stackoverflow btw 😄

seancorfield15:03:05

But this code from Stu's Gist is similar to what you'd need (if you don't want to go with shpec): https://gist.github.com/stuarthalloway/f4c4297d344651c99827769e1c3d34e9#file-missing_keys_specs-clj-L31-L33

borkdude15:03:24

@taylor Oh that’s great. I think I will adopt Schpec

borkdude16:03:15

I want to use schpec in clojurescript, but now I also have to bring in test.check.. ok

borkdude16:03:32

Hmm, I already have that… maybe I should upgrade. I get errors about clojure/spec/gen/alpha

gfredericks16:03:56

What sort of errors?

borkdude16:03:00

• public/js/app.js
WARNING: Use of undeclared Var com.gfredericks.schpec/limit-keys at line 53 src-cljs/dre/components/widget.cljs <-- this is my namespace
WARNING: No such namespace: clojure.spec.gen.alpha, could not locate clojure/spec/gen/alpha.cljs, clojure/spec/gen/alpha.cljc, or JavaScript source providing "clojure.spec.gen.alpha" at line 53 src-cljs/dre/components/widget.cljs
WARNING: Use of undeclared Var clojure.spec.gen.alpha/fmap at line 53 src-cljs/dre/components/widget.cljs

borkdude16:03:06

my ns:

(ns dre.components.widget
  (:require
   [clojure.spec.alpha :as s]
   #_[com.gfredericks.schpec :refer-macros [excl-keys]]
   [dre.page-util :refer [open value-of in-iframe?]]
   [dre.react-util :refer [Collapse DropdownButton MenuItem]]
   [reagent.core :as r]
   [taoensso.timbre :refer-macros [info warn debug]])
  (:require-macros
   [com.gfredericks.schpec :refer [excl-keys]]))

borkdude16:03:55

I first tried the one with #_ but then I get:

No such namespace: com.gfredericks.schpec, could not locate com/gfredericks/schpec.cljs, com/gfredericks/schpec.cljc, or JavaScript source providing “com.gfredericks.schpec” in file src-cljs/dre/components/widget.cljs

gfredericks16:03:13

Does clojure.spec.gen.alpha exist in cljs?

borkdude16:03:58

hm, it appears not no

borkdude16:03:51

ah, cljs.spec.gen.alpha does exist

borkdude16:03:56

have you tried this in clojurescript?

borkdude16:03:10

probably limit-keys should be in a cljc file?

borkdude16:03:57

and some other adaptations

Alex Miller (Clojure team)16:03:15

cljs should rewrite the clojure to cljs one automatically, but you would need it in a cljc file

borkdude16:03:54

@gfredericks you could also put everything in a .cljc file with the help of macrovich… just wrap every macro in macros/deftime

gfredericks16:03:31

@borkdude I have not tried it in cljs; schpec hasn't gotten a lot of attention. I had the silly idea that I could just make a generic bucket to put things in and then people would put things in it and use it, but I think people prefer to make libraries with more specific purposes

gfredericks16:03:48

but I'm happy to incorporate any improvements you have, like making sure it works in cljs

borkdude16:03:19

I’ll make an attempt

mgrbyte16:03:54

hi, I'm wanting to make a model in spec of my data where the values for 2 keys in my map are dependant on another key's value. I've watched Stu's blog post about using gen/bind and gen/fmap; are there any other examples someone could point me to?

anmonteiro16:03:32

@alexmiller we’re very confused about this behavior, wondering if it has come up before:

user> (s/conform #{11 false :foo} false)
:clojure.spec.alpha/invalid

Alex Miller (Clojure team)16:03:50

you should never use logically false values in literal sets

Alex Miller (Clojure team)16:03:07

(and this applies to more than spec - some is another case)

anmonteiro16:03:11

yeah I guess that really messes things up

Alex Miller (Clojure team)16:03:25

use false? as a predicate

Alex Miller (Clojure team)16:03:35

this particular case does not typically come up in general use very often in my experience

anmonteiro16:03:20

what you said actually makes total sense, it just got me puzzled here for a minute

anmonteiro16:03:50

I guess we’ll always need to return logical true for valid? to work

borkdude17:03:38

The errors being:

WARNING: No such namespace: clojure.spec.gen.alpha, could not locate clojure/spec/gen/alpha.cljs, clojure/spec/gen/alpha.cljc, or JavaScript source providing "clojure.spec.gen.alpha" at line 54 src-cljs/dre/components/widget.cljs
WARNING: Use of undeclared Var clojure.spec.gen.alpha/fmap at line 54 src-cljs/dre/components/widget.cljs
when I call the macro excl-keys

borkdude17:03:11

I’ll try to change the require into:

(:require [clojure.spec.alpha :as s]
            [clojure.spec.gen.alpha :as sg]
            [clojure.set :as set]
            #?(:clj [net.cgrand.macrovich :as macros]))
which I think should “just work” ™️ in cljs as well

borkdude17:03:21

hmm same errors

borkdude17:03:34

I give up for now… short on time

gfredericks17:03:39

My apologies. cljc and macros can be a pretty gnarly combination

borkdude18:03:08

No problem man. I’m sure it could work with a small change.

borkdude18:03:29

I also ran into this issue with cljc, macros and clojurescript: https://dev.clojure.org/jira/browse/CLJS-2636

pablore20:03:58

I have a spec like this: (s/def (s/map-of ::id ::person) where ::id must be a property of ::person. Could anyone help me making the spec with s/with-gen so I can generate this map for testing?

guy20:03:14

(s/def :entity.person/id string?) (s/def :entity/person (s/keys :req [:entity.person.id])

guy20:03:20

like that do you mean?

guy20:03:39

what do you mean by property of person?

pablore20:03:14

person is defined in terms of ::id

pablore20:03:24

so when I generate a map of ids and persons, the map of id-persons must have sense

pablore20:03:28

Indexed collections are structures I've come to make a lot of times doing clojure. Ie, I rather do (get-in state [:people id :name]) than have to find the vector index every time.

bbrinck20:03:44

@seancorfield @borkdude keep in mind that any attempt to analyze specs won’t work for multi-specs in CLJS, at least AFAICT

bbrinck20:03:38

(which limits our ability to do things like look for undefined specs, or to automatically add checkers for missing values)

Alex Miller (Clojure team)20:03:10

@pablore I don’t have time to give you a full answer, but I would start with a generator that creates a collection of ::person (which will have ::id’s in it). then gen/fmap over that generator to construct the map from the ids in the generated person entities

Alex Miller (Clojure team)20:03:39

something like (gen/fmap (fn [ps] (zipmap (map ::id ps) ps)) #(s/gen (s/coll-of ::person)))

pablore20:03:23

This almost worked! Just has to replace ::id with :id, because (s/keys) generates with keys with single colons

pablore21:03:42

(defn gen-indexed
  [spec idx]
  (gen/fmap (fn [xs] (zipmap (map idx xs) xs))
            (s/gen spec)))

pablore21:03:49

this would be a more generic version

dottedmag21:03:18

How do I write a spec for [[1]]?

dottedmag22:03:23

Obviously my data is more complicated than that — it's an number of vectors enclosed one in other. I'm trying to use spec for validation, so it's an external data type, not something I can change.

Alex Miller (Clojure team)22:03:28

seriously though, do you know how many levels of vectors?

dottedmag22:03:54

Two levels, it's a parsed CSV.

dottedmag22:03:38

Not a "nice" uniform CSV, a complicated one. It's got a multiline header and a multiline footer, and empty strings delimiting data sections.

Alex Miller (Clojure team)22:03:45

well, spec the inner levels, then spec the outer levels out of those :)

Alex Miller (Clojure team)22:03:16

(s/def ::line (s/coll-of string? :kind vector?))

dottedmag22:03:53

That's what I tried. s/cat on outer level, then s/cat on inner level, and first predicate in inner level tried to match the whole line, not the first element of an array.

dottedmag22:03:32

Let me shorten it a bit

dottedmag22:03:29

(s/def ::header-fields (partial = ["Account Number"]))
(s/def ::account-number (s/and string?
                               (partial re-matches #"^[0-9]{16}$")))
(s/def ::header-info (s/cat ::account-number ::account-number))

(s/def ::delimiter (partial = [""]))

(s/def ::header (s/cat ::header-fields ::header-fields
                       ::header-info ::header-info))

(s/def ::statement (s/cat ::header ::header
                          ::delimiter ::delimiter))

dottedmag22:03:05

This ought to match [["Account Number"] ["1234123412341234"] [""]], but apparently the specs for sequences are flattened.

dottedmag22:03:24

Because

(s/explain ::statement data)
In: [1] val: ["1234123412341234"] fails spec: :bacc.foo/account-number at: [:bacc.foo/header :bacc.foo/header-info :bacc.foo/account-number] predicate: string?

dottedmag22:03:39

Ah, if I add (s/and (s/coll-of string? :kind vector) ...) to the ::header-info then in succeeds.