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.

alexmiller10: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 ???})

alexmiller12:08:00

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

1
alexmiller12: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?

alexmiller14:08:56

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

alexmiller14:08:17

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

alexmiller14: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.

alexmiller14:08:38

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

alexmiller14:08:47

well I guess s/spec

sundbp14:08:53

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

sundbp14:08:00

Meant the spec2 macros

alexmiller14:08:46

can you share more of your code?

sundbp14:08:55

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

alexmiller14:08:54

what is entity?

alexmiller14:08:30

I guess a symbol? kw?

sundbp14:08:56

entity is a kw

alexmiller14:08:20

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

alexmiller14:08:19

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

alexmiller14:08:45

I think you'd want the latter there

alexmiller14: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)

alexmiller14:08:19

that's actually a bug in spec 2 function explication

alexmiller14: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?

alexmiller14:08:49

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

alexmiller14: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.

alexmiller14:08:39

basically you're fixing this issue:

alexmiller14:08:40

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

alexmiller14: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

alexmiller14: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.

alexmiller15:08:22

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

alexmiller15:08:26

as it's not a symbol

alexmiller15:08:37

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

alexmiller15: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

alexmiller15: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.

alexmiller17:08:02

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

alexmiller17:08:03

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

alexmiller17:08:18

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

alexmiller17:08:34

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

alexmiller17:08:16

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

alexmiller17:08:51

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

alexmiller17: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