Fork me on GitHub
#clojure-spec
<
2019-12-18
>
ag21:12:06

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

Alex Miller (Clojure team)21:12:32

can you more clearly state inputs and output?

ag21:12:16

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”

ag21:12:09

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

Alex Miller (Clojure team)21:12:13

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

ag21:12:40

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

ag21:12:03

all that programmatically

Alex Miller (Clojure team)21:12:54

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

ag21:12:21

sure… let’s assume they are 100% there

Alex Miller (Clojure team)21:12:26

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

ag21:12:05

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

ag21:12:19

Thank you Alex… sorry for being so lame at explaining

Alex Miller (Clojure team)21:12:34

np, just trying to impedance match :)

ag21:12:00

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

Alex Miller (Clojure team)21:12:15

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

Alex Miller (Clojure team)21:12:28

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

ag21:12:44

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

seancorfield22:12:45

@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

seancorfield22:12:28

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

ag22:12:35

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

ag22:12:52

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?

ag22:12:50

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?

ag22:12:59

dynamically?

Alex Miller (Clojure team)22:12:16

there are multiple independent questions here

Alex Miller (Clojure team)22:12:20

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]) )

Alex Miller (Clojure team)22:12:37

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.

Alex Miller (Clojure team)22:12:02

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

Alex Miller (Clojure team)22:12:55

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

ag22:12:45

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

Alex Miller (Clojure team)23:12:32

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)

ag23:12:57

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

Alex Miller (Clojure team)23:12:37

what do those strings mean?

Alex Miller (Clojure team)23:12:05

those are nested keys in map data or something?

ag23:12:29

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])

ag23:12:33

and they all may be sitting in different namespaces

ag23:12:15

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

ag23:12:56

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

Alex Miller (Clojure team)23:12:42

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.

Alex Miller (Clojure team)23:12:28

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

Alex Miller (Clojure team)23:12:43

you are working significantly at odds with that idea

ag23:12:15

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

Alex Miller (Clojure team)23:12:21

generically, relational data is sets of maps

seancorfield23:12:18

@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...

ag23:12:29

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

seancorfield23:12:25

(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 ^

seancorfield23:12:29

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

seancorfield23:12:51

(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)

seancorfield23:12:15

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"}

ag23:12:49

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