Fork me on GitHub
#beginners
<
2019-08-31
>
seancorfield00:08:24

When I came to Clojure, I felt like a bit of a fraud since we were doing very boring general purpose stuff with Clojure instead of all the cool stuff it seemed like everyone else was doing...

johnjelinek00:08:28

😆 I'm just trying to do boring general purpose stuff ... just in a way that provides faster feedback loops

johnjelinek00:08:10

I'm a polyglot programmer -- but I'm not particularly good at programming, aside from knowing syntax. I'm particularly good at integration

seancorfield00:08:49

...we were doing JDBC database stuff, text manipulation, basic stuff.

johnjelinek00:08:54

and so, if Clojure can help me fulfill the principles of SOLID without all the ceremony, that's really what I want (and composition instead of inheritance)

seancorfield00:08:32

Yeah, we definitely found Clojure helped us do all of that boring stuff faster

johnjelinek00:08:16

I'm still having trouble telling my colleagues why this is superior:

(def people (atom ()))
(defn make-person [name age] {:name name :age age})
(defn add-person [people person] (conj people person))
(defn save-person! [p] (swap! people add-person p))

alexmiller00:08:14

I would probably not make most of those functions

johnjelinek00:08:26

like ... look at all the getters and setters I didn't have to write

alexmiller00:08:28

and would use their bodies directly when needed

alexmiller00:08:35

then - look, no functions at all :)

johnjelinek00:08:46

I'm not following

seancorfield00:08:47

(swap! people conj {:name "Sean" :age 57})

hiredman00:08:22

embrace the adhoc

johnjelinek00:08:37

I want to ... but, no comprendo

seancorfield00:08:41

Use data, not functions.

seancorfield00:08:00

And use "core" functions instead of writing wrappers.

johnjelinek00:08:04

😕 I thought I was doing functional programming (more than data programming)

hiredman00:08:08

like, if a person is a map with a :name and an :age key, you don't need a function to make one of those

hiredman00:08:17

you can just make one

johnjelinek00:08:29

is that better than using a (defrecord?

johnjelinek00:08:15

is (defrecord just to ease the transition away from OOP?

hiredman00:08:19

there are times when they can be a performance improvement, at the loss of generality(but they try to keep as much map generality as they can)

hiredman00:08:01

but for holding data, maps are the best

johnjelinek00:08:10

so .. would you prefer a spec to define a person over a (make-person if I needed a concise place for someone to know what all goes into a person map?

hiredman00:08:06

sure spec, or just write a person? predicate (which you could use with spec or not)

seancorfield00:08:07

Don't overthink stuff. Simple data is simple. You don't need types or specs for something that simple and transparent really.

johnjelinek00:08:35

this?

(defn person? [p] (not (nil? #(and (:name p) (:age p)))))

seancorfield00:08:01

That won't do what you think

seancorfield00:08:16

#(and ,,,) is a function literal so it'll never be nil

hiredman00:08:38

just #(and (contains? % :name) (contains? % :age))

hiredman00:08:06

no nill checks, no nots

johnjelinek00:08:19

nice:

(def person? #(and (contains? % :name) (contains? % :age)))

hiredman00:08:49

and then if you have more constraints you can add them to the and

johnjelinek00:08:50

Oooh -- and anything that implements this interface is a person

hiredman00:08:02

(number? (:age %))

seancorfield00:08:48

You only need to write as much code as you "care" about...

johnjelinek00:08:31

ok -- so, when you say data not functions, you mean predicates like def person? instead of (defn is-person? [p] ...)

seancorfield00:08:24

No, those are both functions.

johnjelinek00:08:58

ok .. then you mean just make {:name "" :age 0} everywhere instead of making a constructor function

seancorfield00:08:17

Why would you create an empty/invalid person?

seancorfield00:08:22

In OOP we're using to creating "empty" objects but Clojure doesn't do that -- nil is generally "nothing". So data either "exists" (and is usually valid) or it doesn't exist nil.

johnjelinek00:08:27

well, s/""/name s/0/age, but it gets defined in the function instead of going through a constructor function

seancorfield00:08:45

It's just data.

seancorfield00:08:00

You define it where you need it.

johnjelinek00:08:23

in the past, I've found that hard to share with others when I define it exactly where I need it

johnjelinek00:08:39

like, it requires a conversation or perusing the code to know what's needed

seancorfield00:08:47

I'd need more context for that, in order to comment.

johnjelinek00:08:58

instead of just saying here's this api lib, go consume it (also no docstrings)

seancorfield00:08:51

If you have a data structure that is important to your domain and is shared between a lot of functions then, sure, document it with Spec.

seancorfield00:08:24

But specs are "just data" too really.

johnjelinek00:08:34

I suspect a person data structure is important in many domains

johnjelinek00:08:51

I also suspect it's an overloaded name and has different map properties at different parts of the business domain

hiredman00:08:51

the predicate approach can lead in to spec, which is predicate based with additional features for building large complex predicates, and determining which "sub" predicate failed, and for generating data that matches those predicates

seancorfield00:08:05

Remember that (:foo person) isn't going to blow up -- just produce nil if it doesn't contain :foo. Clojure code generally doesn't care about anything it doesn't need.

johnjelinek00:08:49

thanks all for your help -- dinner time

seancorfield00:08:48

Dinner time here too. Friday night at the local brewpub I expect 🙂

calle05:08:22

Hi, I'm following this guide https://clojurescript.org/guides/quick-start It's not going very well.

$ mkdir -p hello-world/src/hello_world
$ cd hello-world/
$ touch src/hello_world/core.cljs deps.edn
$ cat > deps.edn
{:deps {org.clojure/clojurescript {:mvn/version "1.10.520"}}}
$ cat > src/hello_world/core.cljs
(ns hello-world.core)

(println "Hello world!")

$ clj --main cljs.main --compile hello-world.core --repl
-bash: clj: command not found
$ clojure --main cljs.main --compile hello-world.core --repl
Exception in thread "main" java.io.FileNotFoundException: Could not locate cljs/main__init.class, cljs/main.clj or cljs/main.cljc on classpath.
        at clojure.lang.RT.load(RT.java:466)
        at clojure.lang.RT.load(RT.java:428)
        at clojure.core$load$fn__6824.invoke(core.clj:6126)
        at clojure.core$load.invokeStatic(core.clj:6125)
        at clojure.core$load.doInvoke(core.clj:6109)
        at clojure.lang.RestFn.invoke(RestFn.java:408)
        at clojure.core$load_one.invokeStatic(core.clj:5908)
        at clojure.core$load_one.invoke(core.clj:5903)
        at clojure.core$load_lib$fn__6765.invoke(core.clj:5948)
        at clojure.core$load_lib.invokeStatic(core.clj:5947)
        at clojure.core$load_lib.doInvoke(core.clj:5928)
        at clojure.lang.RestFn.applyTo(RestFn.java:142)
        at clojure.core$apply.invokeStatic(core.clj:667)
        at clojure.core$load_libs.invokeStatic(core.clj:5985)
        at clojure.core$load_libs.doInvoke(core.clj:5969)
        at clojure.lang.RestFn.applyTo(RestFn.java:137)
        at clojure.core$apply.invokeStatic(core.clj:667)
        at clojure.core$require.invokeStatic(core.clj:6007)
        at clojure.main$main_opt.invokeStatic(main.clj:491)
        at clojure.main$main_opt.invoke(main.clj:487)
        at clojure.main$main.invokeStatic(main.clj:598)
        at clojure.main$main.doInvoke(main.clj:561)
        at clojure.lang.RestFn.applyTo(RestFn.java:137)
        at clojure.lang.Var.applyTo(Var.java:705)
        at clojure.main.main(main.java:37)

calle05:08:05

I ran sudo apt install clojure to install clojure. Running Debian Buster.

calle05:08:01

$ clojure -r
Clojure 1.10.0
user=>

calle05:08:22

any ideas please?

abdusalam05:08:10

@calle that's interesting, it's like the clojure command is not detecting the local deps.edn at all.

abdusalam05:08:38

otherwise, you would see something like the following:

Downloading: org/clojure/clojurescript/1.10.520/clojurescript-1.10.520.pom from 
Downloading: org/clojure/clojurescript/1.10.520/clojurescript-1.10.520.jar from 

abdusalam05:08:12

for some reason, the clojure package on debian does something slightly different than what the official installation script (https://www.clojure.org/guides/getting_started#_installation_on_linux) does (e.g., the clojure debian package doesn't install the clj shell script). i would try uninstalling that package and install clojure/`clj` following the official installation instructions for gnu/linux.

hiredman05:08:35

previously there was never an official shell launcher for clojure, and whoever made the debian package included some unofficial one, and debian packages take forever to update so 😞

abdusalam05:08:46

yeah, i ran into some weird issues (e.g., missing clj shell script, unexpected ways of handling command line arguments, etc.) while i was working on https://github.com/replit/polygott/pull/28 and https://github.com/replit/prybar/pull/30 and had to resort to the the official installation instructions for gnu/linux.

calle05:08:43

that seem to work much better, thanks

calle05:08:07

how do I keep my clojure updated now that it's outside of apt?

abdusalam05:08:41

well, you may need to check for the cli tools version periodically and run the shell script manually once in a while. 🙂

calle05:08:35

ok, the script removes the old version of clj and installs the new version?

abdusalam05:08:13

unfortunately, no, the shell script doesn't overwrite/remove existing jars (in /usr/local/lib/clojure/libexec) therefore you need to clean that up manually otherwise the presence of multiple jars confuses tools such as cursive (https://github.com/cursive-ide/cursive/issues/2105).

hiredman05:08:19

in general, clojure usually isn't something you install globally

hiredman05:08:38

each project you have will have a dependency on some possible different version of clojure

abdusalam05:08:58

but it does ensure that the clojure/`clj` shell scripts point at the latest installation of clojure binary and jars.

hiredman05:08:07

and whatever tooling you use for your projects will make sure that version is available

hiredman05:08:39

so the clojure language dependency is managed just like any other library dependency for a project

calle06:08:08

Has anyone used cljsjs.aws-sdk-js? https://clojars.org/cljsjs/aws-sdk-js

calle06:08:23

I can't find any examples of how to actually use it...

calle06:08:45

like, what's the equivalent of

const s3 = new AWS.S3()
?

calle06:08:31

clojure doesn't have classes, right?

calle07:08:23

ah, there's a new thing in the js interop https://cljs.github.io/api/cljs.core/#new

calle07:08:19

So this...

calle07:08:50

...generates this.

calle07:08:35

and when I run it:

$ node main.js
/path/to/out/ls_cfn_resources/core.js:5
var s3_526 = (new AWS.S3());
             ^

ReferenceError: AWS is not defined
    at Object.<anonymous> (/home/kabo/Documents/source/ls-cfn-resources/out/ls_cfn_resources/core.js:5:14)
    at Module._compile (internal/modules/cjs/loader.js:701:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:712:10)
    at Module.load (internal/modules/cjs/loader.js:600:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:539:12)
    at Function.Module._load (internal/modules/cjs/loader.js:531:3)
    at Module.require (internal/modules/cjs/loader.js:637:17)
    at require (internal/modules/cjs/helpers.js:22:18)
    at global.CLOSURE_IMPORT_SCRIPT (/home/kabo/Documents/source/ls-cfn-resources/out/goog/bootstrap/nodejs.js:88:13)
    at Object.goog.importScript_ (/home/kabo/Documents/source/ls-cfn-resources/out/goog/base.js:951:9)

calle07:08:11

I feel like I don't understand how that cljs.aws-sdk-js thing is supposed to work

calle07:08:19

hmm, no, I'll have a loo

calle07:08:50

oh, looks nice, I'll give that a go instead

ghadi07:08:27

aws-api doesn't support ClojureScript at this time

andy.fingerhut07:08:46

@calle Not sure if many folks are reading and responding soon at this hour, but the #clojurescript channel might also be a good place to ask your questions.

calle07:08:34

ok, which timezone are most of you in?

calle07:08:40

I guess the US is sleeping now, and Europe is coming online. I'm in New Zealand.

andy.fingerhut07:08:30

I mean, please do go ahead and ask your questions, and interested folks will respond if they are around, of course. Just trying to set expectations that sometimes things may get slightly slower around here on weekends.

calle07:08:53

ah, ok, thanks 🙂

calle07:08:20

so just because clojure can be compiled to the jvm or javascript doesn't mean the modules can be compiled to jvm or javascript?

calle07:08:44

or can I compile clojure to javascript without using clojurescript?

calle07:08:56

confused Calle is confused...

calle07:08:48

I dev a lot of stuff in node.js so I figured it'd be easier for me to build stuff to javascript instead of JVM

andy.fingerhut07:08:16

So Clojure/Java and ClojureScript are two very similar variants of Clojure source code, the former being compilable to JVM byte code, the latter compilable to JavaScript

andy.fingerhut07:08:11

There is enough similarity in the two variants that it is definitely possible to write code that is legal as both variants at the same time.

andy.fingerhut07:08:37

Not sure if that answered your questions, though, so feel free to follow up

calle07:08:23

yeah I think I get it, so aws-api uses some things that aren't available in clojurescript, or just doesn't expose an export that clojurescript can pick up.

andy.fingerhut07:08:50

I believe aws-api is specifically a Clojure wrapper around Java APIs implemented by Amazon.

andy.fingerhut07:08:13

So not compilable to JavaScript

calle07:08:19

hmm, wanted to experiment with AWS Lambdas in clojurescript, as there's less cold start delay in the node lambdas than with java lambdas

calle07:08:19

and I found a serverless-cljs-plugin I thought would be a good way in, as I'm quite familiar with the serverless framework

calle07:08:03

ah, but I see that plugin actually support building to jvm, so I can still do that

andy.fingerhut07:08:18

but I've never used aws-api nor examined its internals, so I may be mistaken in what I have said about it there.

ghadi07:08:39

Tis not a wrapper. It is a client generated from API data supplied by Amazon. No AWS Java libs employed

calle08:08:24

what's the clojure equivalent of the npm/yarn registry?

calle08:08:12

like if I want to do something with timezones I go to https://yarnpkg.com/ type in timezone, and see that moment-timezone has over 15 million downloads, that's a solid bet. Done.

calle08:08:01

how could I have found aws-api besides it being recommended in this channel?

calle08:08:54

can't find aws-api on there though...

calle08:08:54

yeah, I know, I'm playing with plain clojure now

calle08:08:04

I got aws-api to work

ghadi08:08:43

For library discovery, I forget the common websites....

calle08:08:27

maven seems the most complete in terms of having all the packages there

calle08:08:34

but the user experience is horrible

calle08:08:51

clojars seems to have a good UX, but I can't find aws-api on there for example

calle08:08:14

same goes for the clojure toolbox

calle08:08:09

I must be doing something wrong

neo255108:08:48

Anyone has some insight on a project with a lot of interaction between JS and CLJS? To put into context, I found a package in JS which I would like to reuse, but the code heavily exploit mutability and context, and this in JS. I spent 2 days trying to interact with it through reagent, but some components are just impossible to use in it. I also tried to stay as near to JS as possible, but it just did not work. So I decided to give up and write some part of the solution in JS. I wondered if there were some warnings I should be aware of.

calle12:08:54

I can't get map to behave the way I expect it too...

ls-cfn-resources.core=> (def my-list [{:n "a"}, {:n "b"}])
#'ls-cfn-resources.core/my-list
ls-cfn-resources.core=> (map #(:n %) my-list)
("a" "b")
ls-cfn-resources.core=> ((map #(:n %)) my-list)
#object[clojure.core$map$fn__5847$fn__5848 0x779fe00d "[email protected]"]
ls-cfn-resources.core=> ((comp (map #(:n %))) my-list)
#object[clojure.core$map$fn__5847$fn__5848 0x6067c768 "[email protected]"]

calle12:08:21

according to https://clojuredocs.org/clojure.core/map I should be able to leave the coll argument off

alexmiller13:08:44

You’ll need to use partial to get that - (partial map #(:foo %))

alexmiller13:08:49

Also note that instead of #(:foo %) is identical to just :foo

calle22:08:30

That I can just do :foo was definitely a missing piece of the puzzle, thanks 🙂

calle12:08:38

looking at https://clojure.org/reference/transducers it looks to me like map with no coll argument should return a function that takes a seq and returns a seq with the function applied to all the elements in the seq

alexmiller13:08:04

No, it returns a transducer, which is a special kind of reducing function. It’s not like a curried form of the higher arities, it’s a totally different thing

calle22:08:16

... but when used with comp or ->> it behaves just like the curried form?

calle22:08:55

I feel like I need to sit down and spend some time with that transducers doc page. Get to know it a bit better. 🙂

calle12:08:19

simpler example

ls-cfn-resources.core=> (map inc [1 2])
(2 3)
ls-cfn-resources.core=> ((map inc) [1 2])
#object[clojure.core$map$fn__5847$fn__5848 0x3a3e8320 "[email protected]"]
ls-cfn-resources.core=> ((comp (map inc)) [1 2])
#object[clojure.core$map$fn__5847$fn__5848 0x19ec5bcc "[email protected]"]

calle12:08:13

why do I need eduction?

ls-cfn-resources.core=> (eduction (map inc) [1 2])
(2 3)

alexmiller13:08:37

eduction delays it’s eager reduction until use

alexmiller13:08:23

Most often it’s used as a delayed reduction over an external resource, like a file etc

lkowarschick14:08:27

is there some standard way to explicitly say that a function is curried? (i have a lot of predicate functions like, say starts-with?, that when only called with one of two args return a partially applied version of themselves for easier use in map chains and such.. )

alexmiller15:08:01

No, and that's fairly unusual in Clojure. Generally people expect a function ending in a ? to return true/false

lkowarschick16:08:41

I guess I'll create my own best practise then, as long as im working alone on this project ;) i really miss partial application (i know partial exists, but using it feels extremely ineficcent as it's not more readable and considerably longer than lambda..) ill just suffix currying functions with -c or something

alexmiller16:08:44

the idiomatic Clojure alternative is anonymous function syntax #(... %)

lkowarschick16:08:33

Yea thats what i currently use, but it still makes already rather crowded Code Look a lot more complex than it Has to be

lkowarschick16:08:17

And especially when nesting maps, needing to Use (fn [...] (...)) gets old fast

j.m.frith20:08:23

Hi everyone! Am I right with spec that I cannot use clojure.spec.test.alpha/check while functions are instrumented?

j.m.frith20:08:23

Hi everyone! Am I right with spec that I cannot use clojure.spec.test.alpha/check while functions are instrumented?

j.m.frith20:08:18

It seems that the check conforms the inputs before the test. Since I have an

(s/or :case1 (s/keys ...)
      :case2 (s/keys ...))
conforming the value will turn these maps into vectors [keyword map] which does not conform to the spec

j.m.frith20:08:04

An interesting side-note that this means that conformed values do not themselves conform to the spec... feels weird to me.

alexmiller20:08:44

That’s not weird, true for many specs

alexmiller20:08:55

Where are you seeing the conformed values?

j.m.frith20:08:51

When I run stest/check it's giving me failures because the input does not conform to spec

alexmiller20:08:25

I think you are misdiagnosing the problem

alexmiller20:08:37

But you’ll need to share more

alexmiller20:08:46

Might want to move this to #clojure-spec

j.m.frith20:08:06

I didn't realise there was such a channel?

j.m.frith20:08:59

Hmm.. can I move this thread there, or should I create a new one there?

seancorfield20:08:14

There are hundreds of channels -- click Channels in the left nav bar to see them all. Visit #slack-help for assistance in getting around the community here.

seancorfield20:08:26

Just start a new thread there -- but share your code if you can.

j.m.frith20:08:52

Thanks. Will do

joshlemer21:08:52

do symbols which begin with @ have some special meaning in clojure or convention around them?

skuttleman21:08:31

@ is a reader macro. @foo expands to (deref foo). https://clojuredocs.org/clojure.core/deref

joshlemer21:08:58

Oh ok thankyou @skuttleman

calle22:08:54

aha, I've found something that works as I expect it to.

=> (map inc [1 2])
(2 3)
=> ((map inc) [1 2])
#object[clojure.core$map$fn__5847$fn__5848 0x3a3e8320 "[email protected]"]
=> ((partial map inc) [1 2])
(2 3)

calle22:08:14

is there a simple explanation why (map inc) != (partial map inc)?

michael.gaare22:08:21

(into [] (map inc) [1 2])) would be the usual way to use that map transducer

calle22:08:52

ok, I'm using it in comp, can I do ((comp (into [] (map inc))) [1 2])?

hiredman22:08:17

clojure doesn't do automatic currying like that

michael.gaare22:08:27

what are you comping with

calle22:08:42

hmm, ok, so I need to use partial then

calle22:08:44

I'm used to node.js and ramda

calle22:08:34

basically I'm trying to do

R.pipe(
  R.prop('k'),
  R.map(R.prop('n'))
)({ k: [{n: "a"}, {n: "b"}] })

calle22:08:56

which would return ['a', 'b']

calle22:08:29

pipe was easy to recreate

(defn pipe
  [ & args ]
  (apply comp (reverse args)))

michael.gaare22:08:16

so there are two key ideas about transducers. First, and arguably most importantly, is the idea that they separate out the core idea of a some functional transformation, from the mechanics of dealing with input seq and output seq. If you look at the source of the 2+ arity of map, it's a bunch of code that's looking at what kind of input collection it is and handling chunking it up or not appropriately. But the essential idea of map is applying a function to values. So the transducing version of map just does that, and the transducing process itself handles the various types of inputs and output types you might have. For instance, transducers can be applied to streams (eg core.async channels), not just a specific set of clojure seqs. The other idea is that they can be more efficient when doing multiple steps. When using the seq-based transforms, map, filter, partition-all, etc there are intermediate lazy seqs created between each step.

skuttleman22:08:19

I think you're looking for this: https://clojuredocs.org/clojure.core/-%3E%3E

(->> thing :k (map inc))

calle22:08:04

I read that ->> turns thing into a list if it's not a list, which I don't want, right?

michael.gaare22:08:06

you're almost recreating a little piece of transducers with that pipe function

michael.gaare22:08:42

no, ->> doesn't do that. It just threads the result of each form into the last arg of the next form

calle22:08:49

Am I reading the docs wrong? https://clojuredocs.org/clojure.core/-%3E%3E > Inserts x as the last item in the first form, making a list of it if it is not a list already.

michael.gaare22:08:49

(macroexpand '(->> thing :k (map inc))) => (map inc (:k thing))

michael.gaare22:08:03

it's talking about the forms, not the first value

calle22:08:37

ah, the it was a bit unclear

michael.gaare22:08:54

Although (->> thing :k (map inc)) won't actually do what you want since you're passing a collection of seqs in

michael.gaare22:08:14

so thing you're doing with R.pipe ... can be done with threaded form like, (->> thing (map :k) (mapcat :n)), or with transducers like, (into [] (comp (map :k) (mapcat :n)) thing)

calle22:08:51

yup, trying it out now

calle22:08:19

I suspected there was probably some neater way of doing it

michael.gaare22:08:37

you can also remove nils in the transducer version with (into [] (comp (map :k) (keep :n) cat) thing)

calle22:08:23

Sweet, I got it working 🙂

calle22:08:11

To list all AWS resources in all AWS CloudFormation stacks:

(->>
          (aws/invoke cfn {:op :ListStacks})
          :StackSummaries
          (map :StackName)
          (map #(aws/invoke cfn {:op :ListStackResources :request {:StackName %}}))
          (mapcat :StackResourceSummaries)
          (map :PhysicalResourceId)
          sort
          println)

dfehrenbach0423:08:04

I have a question regarding writing specs. lets say I have some map that looks like:

{:attribute-a {:penguin 0
               :otter 3
               :giraffe 5}
 :attribute-b {:section-a {:penguin true
                           :otter false
                           :giraffe true}
               :section-b {:otter 1.0}}}
How am I supposed to write specs for this? :penguin :otter and :giraffe all follow different rules based on where they are inside the map. I heard someone recommend namespaces? (s/def ::attribute-a/penguin int?), but when doing this, the attribute-a namespace “isn’t recognized”. I know I must be missing something, but I’m not sure what it is.

skuttleman00:09:30

I think the problem is that ::attribute-a/penguin is not a valid keyword. It is a short cut for fully qualifying a keyword based on an aliased namespace. You probably want one of the following:

(s/def :attribute-a/penguin int?)
or
(require '[some.other.namespace :as attribute-a])
(s/def ::attribute-a/penguin int?)
In the second example, ::attribute-a/penguin is expanded by clojure's reader to :some.other.namespace/penguin.

dfehrenbach0400:09:03

Thanks for responding! So, if I were defining the map as we have it here. I suppose what I really want to write, and excuse my lack of syntax knowledge.

(s/def :attribute-a/penguin (s/int-in 0 2))
(s/def :attribute-b/otter (s/int-in 0 4))
(s/def :attribute-b/giraffe (s/int-in 0 6))
(s/def ::attribute-a (s/keys :req-un [:attribute-a/penguin
                                      :attribute-b/otter
                                      :attribute-b/giraffe]))

(s/def :section-a/penguin boolean?)
(s/def :section-a/otter boolean?)
(s/def :section-a/giraffe boolean?)
(s/def ::section-a (s/keys :req-un [:section-a/penguin
                                    :section-a/otter
                                    :section-a/giraffe]))

(s/def :section-b/otter (s/double-in :min 0 :max 2))
(s/def ::section-b (s/keys :req-un [:section-b/otter]))
(s/def ::attribute-b (s/keys :req-un [::section-a
                                      ::section-b]))

(s/def ::animals (s/keys :req-un [::attribute-a ::attribute-b]))
(def thing (gen/generate (s/gen ::animals)))
(-> thing :attribute-b :section-b :otter)

dfehrenbach0400:09:42

So……. I just wrote all this out, expecting that the lowest keys would look like :attribute-a/section-b/otter (as one long string) But…. they don’t. And that last line actually works. The first clue you gave me (s/def :attribute-a/penguin int?) was spot on. Thank you! party-corgi

dfehrenbach0401:09:12

Ah heck. Now to figure out how to access something like the :section-a/penguin from another file 😞

schmee01:09:13

you need to require the namespace with the specs in the them if you want to use them outside of that namespace

dfehrenbach0401:09:27

Yeah, so if the specs file has got this at the top:

(ns some-project.some-dir.specs
  (:require [clojure.spec.alpha :as s]
            [clojure.spec.gen.alpha :as gen]))
From an importing file I’ve been doing:
(ns some-project.some-dir.some-other-file
  (:require [some-project.some-dir.specs :as specs])
With this syntax, I can’t just use :section-a/penguin because I can’t do:
(gen/generate (s/gen :specs/section-a/penguin))
I think I have to :refer to something like :section-a/penguin directly in the :require sexp

dfehrenbach0401:09:25

(which works, but isn’t pretty because I may have to refer to a bunch of things 😕 )

skuttleman01:09:30

You shouldn't need to refer the specs to use them. Specs are defined in a global registry (which is why they're namespaced keys). Requiring them should be enough.

dfehrenbach0401:09:28

No matter how deep in the project structure they’re created?

skuttleman01:09:17

If your specs are in namespace d and you evaluate code in namespace a which requires namespace b which requires namespace c which requires namespace d, then you're spec definitions will be evaluated and available for use.

dfehrenbach0401:09:53

I’ve gotta break that down. Let’s check my understanding. Starting with namespace d

(ns a.b.c.d 
  (:require [clojure.spec.alpha :as s]))
(s/def ::penguin int?)
then in namespace a the following should be possible and create a generator?:
(ns a
  (:require [clojure.spec.alpha :as s]))
(s/gen ::penguin)

dfehrenbach0401:09:42

That doesn’t seem to work in off branches (i.e. a spec in a.b.c.d doesn’t seem to be accessible in a.e)

dfehrenbach0401:09:08

Ahhhh why does project organization have to be so tricky 😕