Fork me on GitHub
#clojure-spec
<
2019-08-16
>
metametadata09:08:45

Looking at the latest blog post about spec2: (s/valid? ::s {::f "Bugs" ::l "Bunny" ::x 10} {:closed #{::s}}). Does this mean that if ::f is also a map then in order to close it the user will have to explicitly put it into set too? I.e.: (s/valid? ::s {::f {:a 1 :b 2} ::l "Bunny" ::x 10} {:closed #{::s ::f}}). If yes, then it seems to be inconvenient for cases with nested maps because when validating a top map I don't want to remember and manually specify the "closed-ness" of all the maps used in validation of every particular key.

Alex Miller (Clojure team)10:08:59

Yes, that’s what it means, but it doesn’t preclude some kind of :all setting

metametadata11:08:59

I see, thanks. I was wondering how easy it will be to migrate my current codebase to this approach. As currently the knowledge of closedness is tied to each spec (I have a custom macro: (sp/speced-keys :closed? true :req ...)). And "parent" specs are not aware of this. :all is fine if all the specs are closed but will not work in case some specs are closed and others are open.

metametadata12:08:15

e.g.

::foo = {::a <vector of closed ::x maps> ::b <open ::y map>}
(s/valid? ::foo myfoo {:closed ???})

Alex Miller (Clojure team)12:08:00

Keep in mind also that s/keys itself is being replaced with s/schema and s/select

4
Alex Miller (Clojure team)12:08:34

The closed checking is currently implemented only in s/schema, not s/keys

sundbp14:08:15

I’ve got this use-case: I’ve got a couple of macros/fns that I use to dynamically create a few “concepts” and associated helper fns (stuff like serialization/deserialization etc). As part of that I’m also creating specs. I’m looking at spec-alpha2 and getting stuck wanting to do something like this: (s/register k (s/spec (fn [x] ..something using a captured local value..) This doesn’t fly, the local value (from e.g. an encapsulating let form) ends up not scope captured as I’d expect and instead breaks everything. Any tips how I can accomplish what I need?

Alex Miller (Clojure team)14:08:56

the (fn [x] ...) part here is a symbolic form (not evaluated)

Alex Miller (Clojure team)14:08:17

so if you want to use a local value, you need to do something to construct the right symbolic form

Alex Miller (Clojure team)14:08:42

you could for example use

`( ... ~foo ... )

sundbp14:08:04

ah. ok. i’ll give that a go. the symbol stuff still doesn’t really compute very well with me - it feels like even though spec2 allows being driven by code (as opposed to manual s/def’s), I still don’t find that I’m able to do so doing things the way I expect. The macros still “get in the way” for me a lot of the time.

Alex Miller (Clojure team)14:08:38

there are no macros involved in your code above. how can they be in the way?

sundbp14:08:53

so I tried (s/register k (s/spec* (fn [x] .. ~foo ..))` - no cigar.

sundbp14:08:00

Meant the spec2 macros

Alex Miller (Clojure team)14:08:46

can you share more of your code?

sundbp14:08:55

the 2nd register is what I’m struggling with.

sundbp14:08:56

entity is a kw

Alex Miller (Clojure team)14:08:20

and in how does this not work? error or spec doesn't do what you expect?

Alex Miller (Clojure team)14:08:19

is there some reason you're using s/spec and not s/spec* in last line?

Alex Miller (Clojure team)14:08:45

I think you'd want the latter there

Alex Miller (Clojure team)14:08:15

try spec* in that last line

sundbp14:08:30

(sorry, for screenshots - I don’t have slack access on a remote machine where the code is and can’t copy across)

Alex Miller (Clojure team)14:08:19

that's actually a bug in spec 2 function explication

Alex Miller (Clojure team)14:08:40

the [x] in the fn is getting ns'ed

sundbp14:08:54

thank god for that - I was starting to run out of ideas 🙂

sundbp14:08:20

can I work around that?

Alex Miller (Clojure team)14:08:49

in that fn, instead of x can you do ~'x everywhere?

Alex Miller (Clojure team)14:08:43

I'm not sure if the error here is in your expansion or inside spec 2

sundbp14:08:15

cool. that compiles at least, so hoping that means I’m on my way. wont be able to confirm overall functionality without some more work.

Alex Miller (Clojure team)14:08:39

basically you're fixing this issue:

Alex Miller (Clojure team)14:08:40

user=> `(fn [x] (+ x 1))
(clojure.core/fn [user/x] (clojure.core/+ user/x 1))

Alex Miller (Clojure team)14:08:12

I have seen places where spec 2 has trouble with this, but here I think it's in your expansion

sundbp14:08:38

more context

Alex Miller (Clojure team)14:08:10

the code you now have is not using any macros and there is no "magic" here - it's just passing forms to functions

sundbp14:08:59

I think that macro+adapt-spec wrapper was a trial and error solution to similar issue I had with passing in a fn (e.g. string?). if I already had the fn and not the symbol pointing to the fn at the time I want to s/register I couldn’t work out any way to proceed.

sundbp15:08:08

btw, that seems to get me back on track and define the sort of spec I have in mind. thanks a lot for the help - and let me know if you want any more info in terms of it being me vs spec2.

Alex Miller (Clojure team)15:08:22

if you already have a function object, then you can't go through the symbolic API (spec*)

Alex Miller (Clojure team)15:08:37

but you can directly create a spec object that implements the protocol

Alex Miller (Clojure team)15:08:06

w/the caveat that you're not going to be able to s/form or nest that kind of thing in other symbolic forms

Alex Miller (Clojure team)15:08:58

which is true to some degree in spec 1 too

sundbp16:08:23

yeah. compared to a more data driven API both spec1 and spec2 throws up some speed bumps if one is generating specs by code. but spec2 seems to have enough flex to make it possible to get what I need even if it’s not yet intuitive to me to work in the symbolic space (as compared to more data composition).

sundbp16:08:36

so I’m a bit split on the API so far. it doesn’t feel like “normal” clojure when one comes at it from generating specs in code. however, it does compose fine as long as you’re manually spec’ing things. net-net I’d love a more data-driven approach. I’ve seen https://github.com/metosin/spec-tools/blob/master/docs/02_data_specs.md and perhaps what I’m looking for is doable on top of the symbolic api.

Alex Miller (Clojure team)17:08:02

well there is now a map-focused format as well (as of last week)

Alex Miller (Clojure team)17:08:03

that is almost certainly going to change formats so I wouldn't touch it yet

Alex Miller (Clojure team)17:08:18

{:clojure.spec/op `s/keys
 :req (conj (map first required) ::entity-type)
 :opt (map first optional)}

Alex Miller (Clojure team)17:08:34

is something you could pass to s/spec* for example and get the identical spec

Alex Miller (Clojure team)17:08:16

and there is an s/expand-spec that can do form->map

Alex Miller (Clojure team)17:08:51

but this should be much more amenable to doing spec->map->transform->spec type things

Alex Miller (Clojure team)17:08:18

there are now 3 or 4 different ways to tap into spec creation/transformation and we're looking at some meta stuff on top of that which might make some of them more accessible, but really there's a lot of options now