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

šŸ‘ 4
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))

Alex Miller (Clojure team)00:08:14

I would probably not make most of those functions

āž• 8
johnjelinek00:08:26

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

Alex Miller (Clojure team)00:08:28

and would use their bodies directly when needed

Alex Miller (Clojure team)00:08:35

then - look, no functions at all :)

šŸ™ƒ 4
johnjelinek00:08:46

I'm not following

seancorfield00:08:47

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

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

šŸ‘ 8
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.

šŸ‘ 4
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 šŸ™‚

Calle Kabo05: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)

Calle Kabo05:08:05

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

Calle Kabo05:08:01

$ clojure -r
Clojure 1.10.0
user=>

Calle Kabo05:08:22

any ideas please?

salam05:08:10

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

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

salam05: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 šŸ˜ž

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

Calle Kabo05:08:43

that seem to work much better, thanks

Calle Kabo05:08:07

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

salam05:08:41

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

Calle Kabo05:08:35

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

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

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

Calle Kabo06:08:23

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

Calle Kabo06:08:45

like, what's the equivalent of

const s3 = new AWS.S3()
?

Calle Kabo06:08:31

clojure doesn't have classes, right?

Calle Kabo07:08:23

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

Calle Kabo07:08:50

...generates this.

Calle Kabo07: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)

Calle Kabo07:08:11

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

Calle Kabo07:08:19

hmm, no, I'll have a loo

Calle Kabo07:08:50

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

šŸ‘ 4
ghadi07:08:27

aws-api doesn't support ClojureScript at this time

šŸ˜ž 4
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.

Calle Kabo07:08:34

ok, which timezone are most of you in?

Calle Kabo07: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.

Calle Kabo07:08:53

ah, ok, thanks šŸ™‚

Calle Kabo07: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?

Calle Kabo07:08:44

or can I compile clojure to javascript without using clojurescript?

Calle Kabo07:08:56

confused Calle is confused...

Calle Kabo07: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

Calle Kabo07: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

Calle Kabo07: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

Calle Kabo07: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

Calle Kabo07: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

Calle Kabo08:08:24

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

Calle Kabo08: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.

Calle Kabo08:08:01

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

Calle Kabo08:08:54

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

Calle Kabo08:08:54

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

Calle Kabo08:08:04

I got aws-api to work

ghadi08:08:43

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

Calle Kabo08:08:27

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

Calle Kabo08:08:34

but the user experience is horrible

Calle Kabo08:08:51

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

Calle Kabo08:08:14

same goes for the clojure toolbox

Calle Kabo08:08:09

I must be doing something wrong

David Pham08: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.

Calle Kabo12: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 "clojure.core$map$fn__5847$fn__5848@779fe00d"]
ls-cfn-resources.core=> ((comp (map #(:n %))) my-list)
#object[clojure.core$map$fn__5847$fn__5848 0x6067c768 "clojure.core$map$fn__5847$fn__5848@6067c768"]

Calle Kabo12:08:21

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

Alex Miller (Clojure team)13:08:44

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

Alex Miller (Clojure team)13:08:49

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

Calle Kabo22:08:30

That I can just do :foo was definitely a missing piece of the puzzle, thanks šŸ™‚

Calle Kabo12: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

Alex Miller (Clojure team)13: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

Calle Kabo22:08:16

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

Calle Kabo22: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. šŸ™‚

Calle Kabo12: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 "clojure.core$map$fn__5847$fn__5848@3a3e8320"]
ls-cfn-resources.core=> ((comp (map inc)) [1 2])
#object[clojure.core$map$fn__5847$fn__5848 0x19ec5bcc "clojure.core$map$fn__5847$fn__5848@19ec5bcc"]

Calle Kabo12:08:13

why do I need eduction?

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

Alex Miller (Clojure team)13:08:37

eduction delays itā€™s eager reduction until use

Alex Miller (Clojure team)13:08:23

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

Leon14: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.. )

Alex Miller (Clojure team)15:08:01

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

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

Alex Miller (Clojure team)16:08:44

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

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

Leon16:08:17

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

Jazzer20:08:23

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

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

Jazzer20:08:04

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

Alex Miller (Clojure team)20:08:44

Thatā€™s not weird, true for many specs

Alex Miller (Clojure team)20:08:55

Where are you seeing the conformed values?

Jazzer20:08:51

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

Alex Miller (Clojure team)20:08:25

I think you are misdiagnosing the problem

Alex Miller (Clojure team)20:08:37

But youā€™ll need to share more

Alex Miller (Clojure team)20:08:46

Might want to move this to #clojure-spec

Jazzer20:08:06

I didn't realise there was such a channel?

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

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

šŸ‘ 4
Calle Kabo22: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 "clojure.core$map$fn__5847$fn__5848@3a3e8320"]
=> ((partial map inc) [1 2])
(2 3)

Calle Kabo22:08:14

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

mg22:08:21

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

Calle Kabo22: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

mg22:08:27

what are you comping with

Calle Kabo22:08:42

hmm, ok, so I need to use partial then

Calle Kabo22:08:44

I'm used to node.js and ramda

Calle Kabo22:08:34

basically I'm trying to do

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

Calle Kabo22:08:56

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

Calle Kabo22:08:29

pipe was easy to recreate

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

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

Calle Kabo22:08:04

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

mg22:08:06

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

mg22:08:42

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

Calle Kabo22: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.

mg22:08:49

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

mg22:08:03

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

Calle Kabo22:08:37

ah, the it was a bit unclear

mg22:08:54

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

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

Calle Kabo22:08:51

yup, trying it out now

Calle Kabo22:08:19

I suspected there was probably some neater way of doing it

mg22:08:37

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

Calle Kabo22:08:23

Sweet, I got it working šŸ™‚

Calle Kabo22: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)

šŸ‘ 4
Danny23: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.

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

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

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

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

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

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

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

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

Danny01:09:08

Ahhhh why does project organization have to be so tricky šŸ˜•