Fork me on GitHub
#beginners
<
2020-07-21
>
seancorfield00:07:30

@ericihli I think you want s/merge there? You want to spec a map with all the keys of ::user and also the key :crux.db/id, yes?

seancorfield00:07:52

What you're asking for is something that satisfies ::user and also satisfies a hash map with :crux.db/id -- but nothing generated by ::user is going to also satisfy the :crux.db/id key: s/and conforms against the first spec and flows through all the others -- and when generating, it uses the first spec for generation and then checks that it also satisfies the other specs.

seancorfield00:07:24

(s/def ::user+ (s/merge ::user (s/keys :req [:crux.db/id]))) should do what you want.

seancorfield01:07:31

Also, uuid? generates so you don't need with-gen on that:

user=> (s/def :crux.db/id uuid?)
:crux.db/id
user=> (s/exercise :crux.db/id)
([#uuid "f70a76b2-6395-4920-9b62-cdd6b19e59cc" #uuid "f70a76b2-6395-4920-9b62-cdd6b19e59cc"] ...
user=> (s/def ::user
  (s/keys :req-un [::name ::email ::password ::followed-states]))
:user/user
;; dummy definitions, just to generate something
user=> (s/def ::name string?)
:user/name
user=> (s/def ::email string?)
:user/email
user=> (s/def ::password string?)
:user/password
user=> (s/def ::followed-states string?)
:user/followed-states
user=> (s/def ::user+ (s/merge ::user (s/keys :req [:crux.db/id])))
:user/user+
user=> (s/exercise ::user+)
([{:name "", :email "", :password "", :followed-states "", :crux.db/id #uuid "1bd675c4-e852-402b-aaae-3e88c529c7bf"} {:name "", :email "", :password "", :followed-states "", :crux.db/id #uuid "1bd675c4-e852-402b-aaae-3e88c529c7bf"}] ...

ericihli01:07:43

Ah ahh. Thank you. Yes that was exactly my intention.

matt.sporleder01:07:48

are the /guides/ section of http://clojure.org sort of incomplete-feeling?

dpsutton01:07:41

do you think one of the many sections (getting started, learn clojure, programming at the repl...) are incomplete or you think one or more sections are missing?

matt.sporleder01:07:53

I think there should be more sections 🙂

matt.sporleder01:07:58

they end very abruptly

alexmiller01:07:01

The learn Clojure one is actually only half of the content I have for it :)

alexmiller01:07:23

So yes, it does end abruptly :)

matt.sporleder01:07:49

@alexmiller commit!

alexmiller01:07:13

It’s actually converted from a training course and there’s a fair amount of formatting and rewriting I’ve been doing on it

matt.sporleder01:07:52

I can't wait to read it

alexmiller01:07:41

Well don’t expect it imminently

alexmiller01:07:48

The original is available though

alexmiller01:07:32

But if there is something you would like to see, please file an issue at https://github.com/clojure/clojure-site/issues

matt.sporleder01:07:31

I probably had better suggestions two weeks ago but now I'm down a big rabbit hole of getting editors that work with me

matt.sporleder01:07:37

I've been trying to exit vim for 15 years and then calva (xcode) gives me a big warning about using vim keybindings and a smaller one about osx

dpsutton01:07:55

lots of people use vim for clojure

matt.sporleder01:07:13

installed emacs and god mode was okay

matt.sporleder01:07:52

yeah tpope being the guy behind fireplace made me think it probably did not suck

matt.sporleder01:07:03

but, like I said, I'm not a luddite 😉

matt.sporleder01:07:48

anyway please do commit the rest of the guides stuff

dpsutton01:07:31

oh sorry. i misread that as you were a vimmer but you were having to learn another editor for clojure

matt.sporleder01:07:12

@dpsutton part of the "fun" with learning clojure is taking everything I am comfortable with and tossing it out the window

matt.sporleder01:07:42

@dpsutton however I am a little surprised how tied I am to hjkl and modal editing

chetlind_clojure02:07:12

i use https://github.com/syl20bnr/spacemacs, which is a tricked-out emacs that defaults to vi editing mode. best of both worlds imo.

matt.sporleder13:07:37

yeah I liked it when I tried but it was a little too magical-- did you look at doom emacs?

matt.sporleder13:07:53

editor and env taste is a funny thing

matt.sporleder01:07:08

also the touchbar mac keyboard makes both the escape key bad and and arrow keys bad

matt.sporleder01:07:16

so it's a double whammy 😉

matt.sporleder01:07:12

so I appreciate the http://clojure.org stuff like "don't get too wrapped up in the repl" etc vs the "clojure for the brave and true"'s "here is an emacs config"

matt.sporleder01:07:20

that is a lot of lay on someone

alexmiller01:07:34

I remapped caps lock to escape because of the stupid touch bar

andy.fingerhut03:07:24

I avoided buying a Mac with the touch bar because of what other people have said about having one 🙂

alexmiller03:07:09

I really, honestly, gave it a shot. it is bad in not one, but many ways, some that reinforce each other for extra badness. although I can actually see how regular consumer users could love it.

alexmiller01:07:42

Making escape into a not real key that can be usurped by a modal Touch Bar mode is a crime against humanity

chris35802:07:51

fortunately the vast majority of computers in use today are not afflicted with this "touch bar" antifeature

alexmiller02:07:38

I am otherwise a mostly happy long-time mac user. even though it's terrible, I'm not sure it's terrible enough to make me throw out everything else

chris35802:07:50

fair enough. i understand the frustration of a product you like and rely on being changed in a way you don't like. has happened to all of us i'm sure.

matt.sporleder01:07:43

@alexmiller unlock your computer and quickly touch capslock to get real capslock behavior for a few seconds 🙂

dpsutton01:07:18

My caps lock has been control for years. And now my keyboard has an actual control key there.

seancorfield02:07:53

At the risk of going off-topic, does anyone actually love the MBP Touch Bar? It seems like a really odd UX 👀

alexmiller02:07:08

yes, I know some people that actually love it

fabrao02:07:56

hello all, is there any way to do this? (something [:a :c :d] [1 2 3]) -> {:a 1 :c 1 :d} ?

alexmiller02:07:58

(apply hash-map (interleave [:a :c :d] [1 2 3]))

alexmiller02:07:17

or I guess just (zipmap [:a :c :d] [1 2 3])

fabrao02:07:00

That´s I didn´t remember zipmap thank you Alex Miller

jason35808:07:24

@alexmiller https://github.com/cognitect-labs/test-runner sorry if it's a dumb question; any particular reason why it is being kept unreleased and not on e.g. clojars?

alexmiller12:07:06

It doesn’t need to be an artifact to be used from clj so we’ve never bothered to

mmakgaba08:07:48

Hi all. Hope everyone is safe and good. I have the following error. And I do not know what is causing it now. Uncaught Error: React Refresh runtime should not be included in the production bundle. Where should I start? I remove react fast refresh to "devDependencies"

neo255111:07:21

Is it an error or a warning? Do you Import React Refresh anywhere in your code? (What does React Refresh actually?)

mmakgaba06:07:19

So I see that

helix.experimental.refresh requires `react refresh`, seems like it is what bring it into the production build.

dobbey.have.socks12:07:25

Can you determine if messages were put onto core.async channel which is piped to another chan in some time frame and act accordingly?

drewverlee14:07:54

Maybe also have a timer put time stamps in the channel and then a reader can look for them and the message

drewverlee14:07:18

If time stamp is after then react accordingly

dobbey.have.socks14:07:36

Thanks! Also a question if i have a steady stream of messages hitting my app will it be okay to create channels for batching and partitioning that are piped to chan which is being read from? Number of such chans will be around 1-2k, or there's a better way to partition and batch inputs?

drewverlee13:07:02

Having a large number of go/parking/green/virtual channels is fine. None go channels are backed by the jvm and there are limited amount. If a pub sub model with a que that can drop/slide messages is a good fit then i think your on track. I would say its a healhy enough model to start with discover why you need something else. But i haven't used core async directly in production.

watchtheblur16:07:05

Can I run a test file with clj? I tried running clj /path/to/testfile and I just get a blank line. When I deliberately make a typo on the test file and the namespace being tested, it throws errors.

alexmiller16:07:59

you can - what is your test file doing?

alexmiller16:07:23

a common issue is to do a lazy operation, so lazy that it does not get realized

noisesmith16:07:39

usually you need an extra call to clojure.test/run-all-tests

watchtheblur16:07:41

very simple tests. Like if a function returns the right number

noisesmith16:07:14

unless you put that call in your file itself, or otherwise make it run, loading a test file just defines tests and doesn't run them

watchtheblur16:07:09

How do I call clojure.test/run-all-test directly on the command line with clj?

noisesmith16:07:54

-e '(clojure.test/run-all-tests)' can be added to your command line

watchtheblur16:07:04

I get the following error:

Execution error (ClassNotFoundException) at java.net.URLClassLoader/findClass (URLClassLoader.java:382).
clojure.test

noisesmith16:07:56

bare minimum example:

[email protected] ~ % cat simple-test.clj 
(require '[clojure.test :as test])

(test/deftest foo-test (test/is (= 1 2)))
[email protected] ~ % clj simple-test.clj 
[email protected] ~ % clj -i simple-test.clj -e '(clojure.test/run-all-tests)'

... list of namespaces that exist but don't have tests...

Testing user

FAIL in (foo-test) (simple-test.clj:3)
expected: (= 1 2)
  actual: (not (= 1 2))

... more namespaces that don't have tests ...

Ran 1 tests containing 1 assertions.
1 failures, 0 errors.
{:test 1, :pass 0, :fail 1, :error 0, :type :summary}

watchtheblur16:07:23

oh! I assumed since the test file calls the namespace of the file being tested, I wouldn't need to explicitly call the namespace file. Let me try that

noisesmith16:07:24

but really, for a project, you will want a test runner

noisesmith16:07:38

I see value in learning the actual fundamentals as a beginner

watchtheblur16:07:40

can you recommend a test runner?

noisesmith16:07:50

koacha works well

noisesmith16:07:23

but like I said - I think learning what clojure itself offers (which is quite well featured, and much simpler than the tooling) is valuable

noisesmith16:07:10

eg. you can use (clojure.test/run-tests 'ns) or (clojure.test/run-all-tests) in your repl, without having to restart clojure

watchtheblur16:07:22

oh, I think I read your example wrong. Let me try again... What's the difference between clj /path/to/file and clj -i /path/to/file

noisesmith16:07:56

without -e, the -i is implicit, if you provide -e, you need the -i too

watchtheblur16:07:13

got it. That works! Thanks! I'll give koacha a try too

gtzogana17:07:26

i have a test file with some assertions defined, but the test runner doesn’t seem to notice them (or any logic in my deftests) and i can’t figure out for the life of me why

gtzogana17:07:33

i have some fixtures but they are definitely calling and accepting f

hiredman17:07:39

what runner are you using? are you sure the file defining the tests is loaded before the tests run?

gtzogana17:07:44

clojure.test, just running them in the repl

gtzogana17:07:54

evaluating the buffer and then running the test. but also same deal with lein-test

dpsutton17:07:02

Another common pitfall is to forget to use = in the is form

gtzogana17:07:32

just checked again, = is there

hiredman17:07:34

that definitely sounds like a bogus fixture

gtzogana17:07:09

so i see them getting called, and i can print out the function being passed in (or its var, anyway)

hiredman17:07:21

I would comment out all the tests and fixtures and add a single deftest like (deftest foo (is false))

hiredman17:07:00

and see if that passes or fails, if it fails add back fixtures until it passes

gtzogana17:07:29

great thanks, that seems to be working - will continue

dpsutton17:07:04

What makes you think it’s not working? If you run the tests, depending on how you run them, a successful run might just return nil

dpsutton17:07:44

ser=> (t/deftest foo (t/is (= 1 1)))
#'user/foo
user=> (foo)
nil
but (t/run-tests) will display the report

gtzogana17:07:55

it claims there were 0 assertions, even though i had some defined

gtzogana17:07:07

it’s definitely an issue with an uncaught error in a fixture

hiredman18:07:39

yeah, exceptions in fixtures can do very bad things to test suites

micah68821:07:38

What is the most efficient / idiomatic way to process this kind of data structure? (Apologies for non-idiomatic formatting!) Input:

{
  "/a" {:title "a" :group ["X" "Z"]}
  "/b" {:title "b" :group ["X" "Z"]}
  "/c" {:title "c" :group ["X" "Z"]}
  "/d" {:title "d" :group ["Y" "Z"]}
  "/e" {:title "e" :group ["Y" "Z"]}
  "/f" {:title "f" :group ["Y" "Z"]}
}
Desired output:
{
  "X" {
    "/a" {:title "a" :group ["X" "Z"]}
    "/b" {:title "b" :group ["X" "Z"]}
    "/c" {:title "c" :group ["X" "Z"]}
  }
  "Y" {
    "/d" {:title "d" :group ["Y" "Z"]}
    "/e" {:title "e" :group ["Y" "Z"]}
    "/f" {:title "f" :group ["Y" "Z"]}
  }
  "Z" {
    "/a" {:title "a" :group ["X" "Z"]}
    "/b" {:title "b" :group ["X" "Z"]}
    "/c" {:title "c" :group ["X" "Z"]}
    "/d" {:title "d" :group ["Y" "Z"]}
    "/e" {:title "e" :group ["Y" "Z"]}
    "/f" {:title "f" :group ["Y" "Z"]}
  }
}

hiredman21:07:15

(clojure.pprint/pprint
 (apply merge-with merge
        (for [[k v] {"/a" {:title "a" :group ["X" "Z"]}
                     "/b" {:title "b" :group ["X" "Z"]}
                     "/c" {:title "c" :group ["X" "Z"]}
                     "/d" {:title "d" :group ["Y" "Z"]}
                     "/e" {:title "e" :group ["Y" "Z"]}
                     "/f" {:title "f" :group ["Y" "Z"]}}
              g (:group v)]
          {g {k v}})))
{"X"
 {"/a" {:title "a", :group ["X" "Z"]},
  "/b" {:title "b", :group ["X" "Z"]},
  "/c" {:title "c", :group ["X" "Z"]}},
 "Z"
 {"/a" {:title "a", :group ["X" "Z"]},
  "/b" {:title "b", :group ["X" "Z"]},
  "/c" {:title "c", :group ["X" "Z"]},
  "/d" {:title "d", :group ["Y" "Z"]},
  "/e" {:title "e", :group ["Y" "Z"]},
  "/f" {:title "f", :group ["Y" "Z"]}},
 "Y"
 {"/d" {:title "d", :group ["Y" "Z"]},
  "/e" {:title "e", :group ["Y" "Z"]},
  "/f" {:title "f", :group ["Y" "Z"]}}}

hiredman21:07:59

there are a number of built ins that almost do it, group-by and clojure.set/index

neil.hansen.3121:07:40

Error question here. I don't understand when errors are "thrown" vs. "returned". To me, a "thrown" error (even in the REPL) would print out a stacktrace and a dramatic error message. But I'm finding when I evaluate (throw (ex-info "This is a bad function" {})) in my CIDER REPL, the error is "returned" as #error {:message "This is a bad function", :data {}}. Why do some errors print a stack trace, when others are returned like this?

dpsutton21:07:01

you can evaluate *e in the repl

neil.hansen.3121:07:32

That is useful, thank you, but I'm not necessarily just trying to see the error again, I'm trying to improve my mental model as to why this is happening. I'd like to be able to predict the behaviour of Clojure errors, and I think I must be missing something fundamental about either the REPL, Clojure evaluation phases, or host interop.

alexmiller21:07:59

what you see on a thrown error depends on what kind of repl you have too. the default clojure.main repl does not print the stack trace, it only prints a formatted message.

alexmiller21:07:25

errors are exception objects (subclasses of java.lang.Throwable)

alexmiller21:07:22

REPL is a process - it reads your input (from string to Clojure data), evaluates, and prints (a Clojure return value back to text)

alexmiller21:07:13

when you return an exception object (Exception. "foo") it relies on the printer's understanding of how to print an object to text

alexmiller21:07:36

that, in itself, is subject to modification (by tapping into the print system)

neil.hansen.3121:07:44

Thanks @alexmiller for clarifying, I had a feeling it was REPL-specific. In my case, I'm using ClojureScript, shadow-cljs, and CIDER, (in a NodeJS REPL, to further complicate things), so maybe I'll have to do a little more digging on that stack.

alexmiller21:07:18

oh, yeah, that is another whole thing too

alexmiller21:07:09

when an exception is thrown, it is caught in the Evaluate phase and the REPL machinery has to decide how to print that - that is also configurable and different between repls

neil.hansen.3121:07:39

@alexmiller I stumbled across something of yours earlier today that interested me. This https://archive.clojure.org/design-wiki/display/design/Exception%2Bhandling%2Bupdate.html, from what I understood of it, was a useful chart of the different evaluation phases in Clojure, and the kind of exceptions that can happen in each one. I've been looking everywhere for a resource that really lays out what's going on in each phase, because I feel like that's the only way that I'll be able to "grok" and predict error behavior

neil.hansen.3122:07:22

Clojure's error handling seems like it kind of requires a deeper understanding of the compiler, as well as how it interacts with the REPL and the host language. You mentioned a "first principle" a few messages back when you said that "REPL is a process...", and that was very helpful. I'd love it if you could point me to a resource that I could study that could help me build such a "first principles" understanding of everything that's going on.

smith.adriane22:07:05

@, a "first principles" understanding of everything might be a little too broad a subject for a recommendation. we might be able to provide some good resources if we knew a little bit more about your learning goals: • it looks like you're using clojurescript. how familiar are you with javascript and its error handling? • I've been using clojure for years and never knew or cared to know the details linked in the Exception Handling Update link. I think part of that might be different approach to handling errors in programs generally. I always recommend http://erlang.org/download/armstrong_thesis_2003.pdf for a principled approach to error handling • you mentioned that you'd like to better predict the behaviour of clojure errors. can you give a few examples of the types of surprises you've encountered?

neil.hansen.3122:07:39

Thank you! Great questions. I've got a couple years of familiarity with Javascript. I have none with Java, and it seems that I may still encounter the off Java stack trace when I have a compiler error? I'm still getting a feel for that. I was also surprised this morning to see my REPL "return" an error that looked like #error {:message "This is a bad function", :data {}} (even though, upon some inspection with prn, it seems like nothing was actually returned, the error was just printed the same way as a return value).

neil.hansen.3122:07:11

Perhaps the gaps in my understanding are actually with CIDER, shadow-cljs, and the other tooling that I'm using, and error handling is just where I'm noticing it the most.

neil.hansen.3122:07:12

Thank you for the paper, I haven't come across it before. I guess I've been feeling a little overwhelmed, and I tend to want to just understand how the whole machine works when that happens (however impractical that may be).

smith.adriane22:07:07

the paper might not be related to your interests, but it's such a good paper, I always feel like it's worth recommending

smith.adriane22:07:34

unfortunately, I'm less familiar with the cljs side of things. most of the work I do is on the jvm/clj side

smith.adriane22:07:42

my understanding with the shadow-cljs tooling is that once you have it set up, then whenever you save a file it automagically shows you any compiler errors in a dev interface in the browser

smith.adriane22:07:51

and that you can see runtime errors in the js console

smith.adriane22:07:19

what kind of projects are you interesting in building with cljs? are you using a template of some sort to get started?

neil.hansen.3122:07:44

It's very much related! I've spent the last few days reading up on error handling strategies (railway-oriented programming, <? and go-safe, various clj libraries).

neil.hansen.3122:07:23

I'd like to pick away at a web application over the next couple months with re-frame, and I'm going through Eric Normand's (really excellent) courses on http://PurelyFunctional.TV. I'd like the server side to be Clojure or ClojureScript. As I've never worked with the JVM before, I thought I'd mess around with ClojureScript and NodeJS so I could at least build on my Javascript background.

neil.hansen.3123:07:46

Right now, for some practice, I'm working on a command line tool in ClojureScript/NodeJS to do some basic JPEG image processing. I thought I'd try using the zeit pkg tool for fun to make a binary (which, so far, has worked magnificently).

neil.hansen.3123:07:03

I'm not using any templates so far outside of the shadow-cljs tooling (as I mentioned, I'm one of those slow, impractical, "first principles" kind of people.)

neil.hansen.3123:07:17

Shadow-cljs has done a really good job for me so far. It's possible that I just need to keep playing around to "get a feel" for everything rather than looking for a rigid study path. I think I've just felt a little "pushed off the deep end" after finishing basic Clojure resources (Brave/True, Clojure from the Ground Up, etc.) and wanting to begin a "real application". I think what I'm looking for is more of that "step 2" knowledge, particularly around the crossover of language/tooling.

smith.adriane23:07:16

yea, unfortunately, when I was doing cljs work more heavily was years ago and I feel like the best practices have evolved quite a bit since then

smith.adriane23:07:40

but that seems like a good enough description to possibly get some recommendations

smith.adriane23:07:05

you may want try asking in the #clojurescript channel for something that fits: > I think I've just felt a little "pushed off the deep end" after finishing basic Clojure resources (Brave/True, Clojure from the Ground Up, etc.) and wanting to begin a "real application". I think what I'm looking for is more of that "step 2" knowledge, particularly around the crossover of language/tooling.

smith.adriane23:07:57

it does seem like the browser/javascript ecosystem is fairly complicated, even without the extra layer of clojurescript

smith.adriane23:07:59

which may have already been going through

smith.adriane23:07:56

since you're interested in a first principles approach, I might also try just fiddling with the google closure compiler itself, https://developers.google.com/closure/

smith.adriane23:07:50

all of clojurescript and its tooling is all wrapping the google closure compiler, so if you want to better understand the layers involved, it's pretty good place to start

smith.adriane23:07:48

oops, this is the link to the actual compiler, https://developers.google.com/closure/compiler

neil.hansen.3123:07:52

Great advice. Thanks for spending so much time with me on this. I'll study the resources you've sent, and if I'm still searching I'll mention it in the ClojureScript channel.

smith.adriane23:07:52

and if you've never read through the source of clojure.core, it's pretty neat. you can watch the language build itself: https://github.com/clojure/clojurescript/blob/master/src/main/clojure/cljs/core.cljc

tjb22:07:06

hey beginners! i want to give a s/o to this channel for helping me get started with clojure and always answering my questions. with that said i want to give back. so i create a blog post on creating an https://dev.to/tjb/api-server-in-clojure-part-1-4h0g. i hope this helps any other beginner out there trying to figure out how to get an api server up and running from scratch. cheers to everyone here and thank you a million times for helping me with everyone. (i hope i am not breaking any rules. if i am i can delete the post).

tjb22:07:06

hey beginners! i want to give a s/o to this channel for helping me get started with clojure and always answering my questions. with that said i want to give back. so i create a blog post on creating an https://dev.to/tjb/api-server-in-clojure-part-1-4h0g. i hope this helps any other beginner out there trying to figure out how to get an api server up and running from scratch. cheers to everyone here and thank you a million times for helping me with everyone. (i hope i am not breaking any rules. if i am i can delete the post).

seancorfield23:07:35

If you're using Leiningen, you don't need to "install Clojure". You just need a JVM (JDK). Leiningen's shell script handles all the installation after that.

seancorfield23:07:06

The "install Clojure" you link to would get you the Clojure CLI -- which you can use instead of Leiningen.

seancorfield23:07:26

seancorfield/next.jdbc "1.0.13" -- that's a pretty old version of next.jdbc. Since this is a new article, you should update to a newer release, check your app works as expected, and update the article so folks are encourage to use an up-to-date version.

seancorfield23:07:26

"Run lein deps to install these dependencies." -- that not needed these days (it used to be, years ago, but Leiningen auto-installs the dependencies when it runs now).

tjb23:07:21

this is fantastic feedback let me edit these tonight. i have only had none clojure friends read it over so this is really good feedback. thank you sean!

seancorfield23:07:01

"You will use the thread macro to start the component and create a system map." -- Your code doesn't need the thread macro here: you just have (-> (one-expression)) -- not sure what you intended here?

seancorfield23:07:15

stop-server doesn't actually stop the server:

(defn stop-server [server]
  "Helper function to stop the server when the component's stop function is called"
  (when server
    (dissoc server :server)))
Did you mean to call (.stop server) ?

seancorfield23:07:39

You're passing (:server this) into this function. You want to either (assoc this :server nil) or (dissoc this :server) after calling stop-server in your stop lifecycle function.

seancorfield23:07:23

i.e.,

(stop [this]
    (stop-server (:server this))
    (dissoc this :server)))

seancorfield23:07:40

As a point of style, the let is redundant here:

(defn start-server [port]
  "Helper function to start the server when the component's start function is called"
  (let [server (jetty/run-jetty handler {:port (Integer. port)})]
    server))
could just be
(defn start-server [port]
  "Helper function to start the server when the component's start function is called"
  (jetty/run-jetty handler {:port (Integer. port)}))

seancorfield23:07:22

Also, nearly all of your docstrings are in the wrong place: it should be

(defn function-name
  "The docstring"
  [args go here]
  body-of-function)
As you have written it, with the string after the arg list, it will be evaluated as the function expression of the function and then thrown away. See:
user=> (defn foo [x] "This string" (inc x))
#'user/foo
user=> (foo 1)
2
user=> (doc foo)
-------------------------
user/foo
([x])
nil
user=> (defn foo "This string" [x] (inc x))
#'user/foo
user=> (foo 1)
2
user=> (doc foo)
-------------------------
user/foo
([x])
  This string
nil
user=> 

seancorfield23:07:26

Happy to review parts 2 & 3 before publication if you want?

noisesmith23:07:32

aside from the clojure specific stuff - the left margin is so narrow it makes the page hard to read

noisesmith23:07:02

that's likely an issue with the platform you are using though

tjb00:07:09

@ thank you so much for the feedback! i sent this to about 5 friends and only got minor feedback from 2 people so i thought this would be good to go. sometimes you just need to ship and iterate