clojure-spec

About: http://clojure.org/about/spec Guide: http://clojure.org/guides/spec API: https://clojure.github.io/spec.alpha/clojure.spec.alpha-api.html
ag 2019-12-18T21:46:06.031300Z

how can I ns-resolve symbol a spec sitting in a different namespace based on a given string?

alexmiller 2019-12-18T21:47:32.032200Z

can you more clearly state inputs and output?

ag 2019-12-18T21:49:16.033700Z

I want something like:

(ns-resolve
 'my-specs
 (symbol "foo"))
but for a spec. How can I resolve ::my-specs/foo when given two strings “my-specs” and “foo”

alexmiller 2019-12-18T21:49:57.033900Z

resolve to what?

alexmiller 2019-12-18T21:50:02.034200Z

a spec object?

alexmiller 2019-12-18T21:50:29.035300Z

a fq keyword?

ag 2019-12-18T21:51:09.036300Z

yeah, let’s say I want to validate a spec, but instead of spec I have its name as a string

alexmiller 2019-12-18T21:51:13.036400Z

my-specs is an alias only meaningful in the context of a namespace. is that the current namespace or some other one?

ag 2019-12-18T21:54:40.039800Z

so let’s say I have a bunch of fields that I extracted from e.g. SQL query, it’s a vector of ["foo" "bar"]. I have a namespace my-specs with two specs in it: (s/def ::foo string?) and (s/def ::bar boolean?)… now I want to validate data, or generate data, basically I need to “get” those specs

ag 2019-12-18T21:55:03.040200Z

all that programmatically

alexmiller 2019-12-18T21:55:54.041400Z

do you know that all of these specs are in my-specs?

ag 2019-12-18T21:57:21.043Z

sure… let’s assume they are 100% there

alexmiller 2019-12-18T21:57:26.043100Z

if so (s/get-spec (keyword "my-specs" "foo")) should work?

ag 2019-12-18T21:58:05.043800Z

OMG… there’s a literally a function called get-spec… LOL

ag 2019-12-18T21:58:19.044200Z

Thank you Alex… sorry for being so lame at explaining

alexmiller 2019-12-18T21:58:34.044500Z

np, just trying to impedance match :)

ag 2019-12-18T21:59:00.045100Z

however… what if I want to check if the spec is indeed there? 🙂

alexmiller 2019-12-18T21:59:15.045300Z

well if you get nil, it's not there :)

alexmiller 2019-12-18T21:59:28.045800Z

you can also call (s/registry) to just get the full map too

ag 2019-12-18T21:59:44.046200Z

ah… okay… awesome… exactly what I needed. Thanks again!

seancorfield 2019-12-18T22:00:45.046900Z

@ag expectations.clojure.test does a dynamic lookup like that to let you "expect" values conform to specs: https://github.com/clojure-expectations/clojure-test/blob/master/src/expectations/clojure/test.clj#L34-L40

seancorfield 2019-12-18T22:01:28.047900Z

(the dynamic require/resolve is so the code can run on Clojure 1.8)

ag 2019-12-18T22:01:35.048100Z

Oh… that’s nice. I’ll give a gander as well. Thank you Sean!

ag 2019-12-18T22:48:52.049200Z

hey friends… another dynamic lookup related question: if I have a (s/keys) spec and string representation of one of the fields how do I get-spec of that? e.g: I have: ::foo-spec/foo defined as:

(s/def ::foo 
  (s/keys :req-un [::bar-spec/bar]))
and in bar-spec ns I have:
(s/def ::bar (s/keys :req-un [::name]))
and I have strings “foo-spec”, “foo” and “bar.name” what’s the best way to get-spec of ::bar/name ? How can I make it work for multiple levels of nesting?

ag 2019-12-18T22:53:50.051Z

meaning I can get-spec/foo but now I need to “analyze” its :bar field, I have no idea where it sits, is there a way to find it out?

ag 2019-12-18T22:53:59.051300Z

dynamically?

alexmiller 2019-12-18T22:54:16.051700Z

there are multiple independent questions here

alexmiller 2019-12-18T22:55:20.052800Z

re understanding the structure of a keys spec, you can use s/form to get a fully resolved form representing the spec (so you'd see (clojure.spec.alpha/keys :req-un [:bar-spec/name]) )

alexmiller 2019-12-18T22:57:37.054800Z

finding the subspecs inside that spec is a matter of fishing for it (made somewhat complicated by the and / or support in s/keys). There are a variety of ways to tackle that, all a little meh, that's something we're looking at having better answers for in spec 2.

alexmiller 2019-12-18T22:58:02.055200Z

if you go that path, you have only fully-qualified subspecs, so you can just pass them to s/get-spec

alexmiller 2019-12-18T22:58:55.055700Z

and re nesting, there is no such thing as ::bar/name in this case - you've just smooshed together two independent things there

ag 2019-12-18T22:59:45.056600Z

> you’ve just smooshed together two independent things there ehmm… I’m just trying to illustrate that I needed nested lookup…

alexmiller 2019-12-18T23:01:32.059600Z

just saying that the specs themselves are not "nested" and have no naming relationship. spec references are always via fully qualified keywords (even if you use autoresolved names to specify them)

ag 2019-12-18T23:02:57.060900Z

yeah, it seems this isn’t as simple as I thought it would be… so basically I wanted to figure out specs for a vector of strings like:

["account.id" "account.contact.first-name" "account.contact.address.city"]
given that all specs are there with the relations set between them

alexmiller 2019-12-18T23:07:37.061100Z

what do those strings mean?

alexmiller 2019-12-18T23:08:05.061400Z

those are nested keys in map data or something?

ag 2019-12-18T23:10:29.062500Z

so there’s:

(s/def account (s/keys :req-un [::id ::contact])
(s/def contact (s/keys :req-un [::first-name ::address])
(s/def address (s/keys :req-un [::city])

ag 2019-12-18T23:11:33.063Z

and they all may be sitting in different namespaces

ag 2019-12-18T23:13:15.064500Z

now without knowing the ns of city or contact but knowing where account resides and given a string “account.contact.address.city” I want to get-spec of ::address/city

ag 2019-12-18T23:20:56.065700Z

oh.. well, it gets a tad bit crazier when some fields are s/nilable

alexmiller 2019-12-18T23:30:42.066400Z

the whole point of having namespaces is being able to differentiate things. seems like you're working really hard to rebuild what that gives you.

alexmiller 2019-12-18T23:31:28.067Z

the whole idea behind spec is that attributes are the main source of semantics and maps are weaker aggregations of those

alexmiller 2019-12-18T23:31:43.067400Z

you are working significantly at odds with that idea

ag 2019-12-18T23:33:15.068100Z

So if you want a spec that describes a relational data, how would you do it?

alexmiller 2019-12-18T23:35:21.068800Z

generically, relational data is sets of maps

seancorfield 2019-12-18T23:37:18.071400Z

@ag it would probably help you to remember to use explicit qualifiers (on keywords) in your spec definition and stop using :: at least until you get your head around this...

ag 2019-12-18T23:37:29.071900Z

so the above snippet with account -> contact -> address is okay?

seancorfield 2019-12-18T23:38:25.073500Z

(s/def :person/account (s/keys :req-un [:account/id :account/contact])
(s/def :account/contact (s/keys :req-un [:contact/first-name :contact/address])
(s/def :contact/address (s/keys :req-un [:address/city])
for example ^

seancorfield 2019-12-18T23:39:29.074500Z

That also gets you close to the "relational" model if you look at something like next.jdbc querying a database since it will use qualified keywords for column names, based on the table they are in...

seancorfield 2019-12-18T23:40:51.075900Z

(although there you have to reify the primary keys so you'd most likely have :account/id :account/contact_id and the latter would be the FK into the :contact table and its :contact/id column etc)

seancorfield 2019-12-18T23:43:15.077300Z

If you did the join with next.jdbc/execute! you'd end up with a flat map containing

{:account/id 123, :contact/first-name "Ag", :address/city "Wherever"}

ag 2019-12-18T23:43:49.077700Z

yeah, I see how this can simplify a few things.