Fork me on GitHub
Eric Ervin00:06:45

I really adored the 1st edition of Dmitri Sotnikov's web development book. As I remember it, it built everything up step by step with Ring and Compojure. Plus the appendix that summarizes Clojure.


Hm. $31 in iBooks... OK.


Let's give Dmitri and Apple some money.


Hmm, is Luminus purely server-side? I thought it had a cljs front end in the template? Or do you have to specify more options to get that?


It uses profile flags for different features, the minimal app will just be Ring + Reitit, and then you can add db, auth, cljs, etc


I think all the cljs stuff is opt-in with luminus


there's many flags documented in the readme, ClojureScript is one of the sections


so you tell the template which extra libs / features to include


Yeah, just looking at that... one day I'll find the time to go back and learn "modern cljs" tooling and try this out again... last time I touched cljs was 2015 I think.


my knowledge of all of this is expiring fast as well, just over a year ago I worked full time on an end-to-end cljs->clj webapp, but even that was architected much longer ago, and I haven't had to work with any cljs stuff in the last year


I'm told that React is the mostest awesomest thing out there (and it sure does look like it does some slick stuff), which begat Reagent, etc.


(it can't be any worse than trying to figure out what the hell is going on in The PHP Code That Time Forgot)


it's definitely going in the deep end if you are just now learning Clojure as well


(if you know differently, don't disabuse me of that notion, please)


same way the cat learned to swim!

Eric Ervin00:06:39

Don't try to learn a new IDE as well


Yeah, back in 2014/2015 at work we built a proof of concept real-time dashboard with Om (which wraps React) and Sente (wrapping WebSockets). Then we re-built it with Reagent (which we preferred). But it was a painful experience and tooling was... basic... Things are much smoother now and there are many more options in the cljs world for framework-y kind of stuff.


Yeah, I'm sticking with vim/fireplace.


A friend/colleague of mine swears by Emacs, but I was much happier when I learned there was vim integration for it.

Eric Ervin00:06:24

Trying to learn Clojure and a new IDE is a classic nOOb blOOper


I should note: I'm not completely at sea, I'm mostly Perl (done a little bit of Catalyst) and I had a course in Lisp in the distant past, so it isn't like I'm completely adrift in a sea of parentheses.


I should set aside a weekend when my wife's away to go back and learn/re-learn ClojureScript/shadow-cljs/re-frame I think...

Ahmed Hassan03:06:02

Have a look at Keechma and Hoplon too.


Heh, we'll see how far I get 🙂

Eric Ervin00:06:44

Sounds to me like time better spent playing guitar or Magic the Gathering, but you're deeper into the Clojureverse than I am.


OK, well, thanks folks - got the book, let's see where it goes from here. Thanks for the help!


@ericcervin I don't play games at all and, despite trying to learn trumpet, keyboards, guitar over the years I am resigned to being completely tone deaf and non-musical 🙂 Programming is what I've always done for fun 🙂

👍 4
Eric Ervin00:06:57

When we met IRL, you told me about the shirts.


Heh, yeah, I have hundreds of T shirts for both concerts and conferences. I was just chatting with my wife yesterday as we were folding laundry and marveling at some of my near-30-year-old Ts that still survive 🙂


Try the ukulele - it's a lot of fun, and can be learned fairly quickly.


I'm not a string-instrument person, ran into a ukulele teacher in the violin shop where my wife was getting her violin fixed; teacher slammed a uke in my hand, I was hooked.


So Amanda Palmer tells me (re: ukulele) 🙂


and I can highly recommend shadow-cljs, it makes working with NPM modules completely seamless


it also works well with tools.deps from what I saw


@yogthos If I buy the 3rd ed e-book, is it "complete" as in 2nd ed but just getting rewrites as you work toward the final date? Or does it only containing parts of the book as you (re)write them?


My wife leaves for the East Coast on Thursday night (to judge a cat show, back late on Monday), so my weekend will be getting my head around whatever this created:

lein new luminus learning +reitit +jetty +h2 +re-frame +shadow-cljs +auth-jwe


Feel free to ping me about it if you get stuck on anything


Thanks. You may regret that offer 😉


That would indicate that the template or the docs need improvement :)


The PDF of your 3rd ed is in my browser for the weekend reading/learning!


excellent, @U0NLT4Z2Q would be interested in feedback as well


hmm... would the "yogthos" mentioned in this book be the same one on this channel?


Indeed it would :)




... Given that you're skilled at explaining this stuff to folks who are not necessarily used to using it... do you have any insights into my question(s) above? Does any portion of it make sense?


what were the specific questions again? 🙂


3rd ed will be a full rewrite, so chapters are going to appear as me and Scot finish them


A conundrum then: do I buy the 2nd ed so I can learn Luminus this weekend, or do I buy the 3rd ed and see how much is available so far? 🙂


We've got 5 chapters done so far, so all the basics are there


Re-frame, as, and swagger are covered


The next chapters will focus on stuff like auth, file uploads, packaging, testing, and deployment


So hopefully enough content already to be useful :)


For $26, I'll drop the hammer on that then... thanks! Yours is one of the few Clojure books I don't have because I've built lots of Clojure web apps and have been putting off ClojureScript but this seems like a good opportunity to "learn up".


Awesome, hopefully it lives up to the expectations :)

👍 4

(clojure.repl/doc write-secret)
; =>

([secret value])
  write-secret writes a secret
  args: [:dal.secretsmanager/secret :dal.secretsmanager/value]
  ret: map?
Is there an idiomatic way to figure out what a secret or value contains or how to tell if they're strings or maps or whatever?


@johnjelinek call doc again on the specs


oic ... so, I need a function that will walk the spec tree if I want to get a good feel for what's all required for my inputs


You can call spec.gen/sample on it, too


It will show you randomly generated examples of the map

👍 4

is there any up to date guidance on maps vs. records? with extends as metadata and spec, it seems like there's a compelling reason to only use maps or at least heavily prefer them.


maps 99% of the time


👍 thanks!


minus the "type participating in protocol" part now I suppose


The protocol has to have opted in to metadata use at the definition point.


The TL;DR is "use maps" unless you can't, or you have something performance sensitive where you can show records are faster.


As an example, in next.jdbc which is heavily protocol-based, only two protocols are extensible via metadata because either it doesn't make sense for the others to be or I specifically do not want folks to do that.


If you want to use namespace qualified keys, you also cannot use records.


Is order of insertion guaranteed in maps?


@lockdown- hash maps are unordered.


If you want a collection that respects "insertion", you should use a list or a vector (depending on how you want to expose the order).


yep, going to use a vector of vectors of map


@seancorfield you mentioned REBL the other day, it only runs in-process, not network support correct?


@lockdown- Yeah, in-process. Over-the-wire opens up a giant can of worms 🙂 Rich talked a bit about it on the #rebl channel early on as something they don't want to tackle (at least, not yet). Serialization & deserialization is hard and there are issues around how to control sequence realization etc.


@deleted-user is that a fixed set of values?


So you have a set that can contain a subset of that set of values.


(s/def ::direction #{:north :south :east :west})
(s/def ::dir-set (s/coll-of ::direction :kind set? :min-count 0 :max-count 4))
(fixed min/max, and now fixed :kind)


kind needs a single :: :


you have to navigate the docs a bit, it points to a function every IIRC


which has the options


::direction is the name of a spec


user=> (s/def ::direction #{:north :south :east :west})
user=> (s/def ::dir-set (s/coll-of ::direction :kind set? :min-count 0 :max-count 4))
user=> (s/exercise ::dir-set)
([#{:south} #{:south}] [#{:west :east :north} #{:west :east :north}] [#{:south :east :north} #{:south :east :north}] [#{:south :north} #{:south :north}] [#{:west :south :east} #{:west :south :east}] [#{:south :east :north} #{:south :east :north}] [#{:west} #{:west}] [#{:north} #{:north}] [#{:west :south :east} #{:west :south :east}] [#{:south :north} #{:south :north}])


specs are predicates


sometimes docstrings are a bit terse


next section in the reference guide covers the registry


you can register predicates under global (namespace-qualified) names


and combine them together


Because that's how specs work 🙂


It's a global registry, implemented in the spec library


nah you didn't miss that


it's spec, the library, resolving :qualified/keywords to specs using its registry


covered in Registry, I think: > A registered spec identifier can be used in place of a spec definition in the operations we’ve seen so far - conform and valid?.


they're generally interchangeable


I'd probably advise talking to Alex Miller before you spend too much time on it -- and he's on vacation this week.


He'd certainly be able to give you guidance on where (or even if) certain sections could reasonably be updated.


user=> (doc s/spec? ) ------------------------- clojure.spec.alpha/spec? ([x]) returns x if x is a spec object, else logical false


it's late here 😴


"spec object" is something very specific.


The key section is what Ghadi linked you above.


"Any existing Clojure function that takes a single argument and returns a truthy value is a valid predicate spec. ... Here we are passing a predicate which is implicitly converted into a spec. "


Later on "Note that again valid? implicitly converts the predicate function into a spec. "


And in the info box about coll-of: "Both coll-of and map-of will conform all of their elements" -- conform requires a spec


The docstrings use pred rather than spec because spec means something specific in much of the library (and this delineation is much more clearly drawn in spec-alpha2).


Oh, yeah, it's a reasonable criticism that the docs are assuming connections that aren't at all obvious.


Part of the problem is that spec is all about composing predicates to create specs -- but there's no good term for the intermediate between a "predicate function" and a "predicate spec" that's nice and short and could be used everywhere that pred is currently used to imply "predicate spec".


@deleted-user could you read over the beginning of this section and see if it helps clarify the intent?


If that helps, it's likely that when spec-alpha2 moves forward, (some of) that language will be lifted up into the spec guide.


No. But that's the "next generation" of spec and that's where things are going.


So I was trying to see if the more clearly delineated terminology there helped at all. It seems not.


Hey, I trying to try out the J9 VM. On Mac, with (now) two jdks installed via Homebrew. Java in a shell runs the JAVA_HOME version just fine, but I’m not certain lein or Clojure do. Where/how do they find what JDK/JRE to run on?


I’m doing the search thing, but there is a lot of information of various quality and uptodateness. 😅

Alex Miller (Clojure team)10:06:29

Clojure 1.10.x should run on any Java 8+


Does it use JAVA_HOME to find the java version to use?


If using lein you could do lein --version


it should use whatever java is on your path


Btw. I use jenv on Mac OS and while it's not perfect, it is the best thing I've found for managing java installations on Mac


hey @U06BE1L6T I’m finding sdkman works for me for managing jdks.


haven't tried that - might be good, thanks!


I’m trying to get jenv to work, but I’m not experienced enough. I have installed openjdk12 via brew with both the regular VM and with the openJ9 VM. jenv seems to not manage different installs of the same version... I’ll keep trying. 🙂


You probably need to add a new installation via jenv add


Also when you change the installation you often need to start a new terminal window or at least issue the cd command. Finally, export plugin might be useful:

jenv enable-plugin export


I’m not familiar with jenv, how does it differ from lets say this kind of .zshrc config?

export JAVA_8_HOME=$(/usr/libexec/java_home -v1.8)
export JAVA_11_HOME=$(/usr/libexec/java_home -v11)

alias java8='export JAVA_HOME=$JAVA_8_HOME'
alias java11='export JAVA_HOME=$JAVA_11_HOME'

# default to Java 11


setting JAVA_HOME doesn't mean that you actually have proper java on your path. That is following may return completely different version:

java -version


Many tools respect the JAVA_HOME property but that's not always the case


Ok I see, thx!


is there any npm type website to find cljs libraries


i am looking for a form validator. so where can i search when I need such stuff


that has wrappers for JS libraries, i’m not sure if it is of any help if you want pure cljs libraries


yes I know this. I was looking for pure cljs packages


is it ok to use old libraries in cljs


or I should avoid as I would do in JS libraries


@m373h4n It is ok to use old libraries in ClojureScript. There are rarely breaking changes, if that's your concern.

Cas Shun13:06:36

I'm trying to write a macro which (among other things) expands to the value of a form, and the literal form passed in. So (testmacro (+ 1 2)) would return something like {:val 3 :form (+ 1 2) }. Here's my poor attempt:

(defmacro testmacro [x]
  `(let [form# x
         val# ~x]
     {:val val# :form form#}))
I'm somewhat confused as to why ~x works but x does not here?


call (macroexpand '(testmacro (+ 1 2)))

Cas Shun13:06:55

@ghadi I have, I get {:val 3, :form user/x}. I'm unsure of how to get what was passed to the macro rather than x when expanded.

Adrian Smith14:06:20

Is there anything out there that would read in SQL strings then outputs honeysql data structures? Would be cool to have for documenting how to use Honeysql


@auroraminor x is within syntax quote and therefore resolves to an x that you've previously defined. On my fresh REPL I get this:

user=> (testmacro (+ 1 2))
Syntax error compiling at (REPL:1:1).
Unable to resolve symbol: x in this context
To fix the macro you should quote (not syntax quote) unquoted x
(defmacro testmacro [x]
 `(let [form# '~x
        val# ~x]
    {:val val# :form form#}))
I'd write it like this:
(defmacro testmacro [x] `{:val ~x :form '~x})
user=> (testmacro (+ 1 2))
{:val 3, :form (+ 1 2)}

Cas Shun15:06:19

@clojurians971 thanks, that does work.

Cas Shun15:06:01

but, I'm somewhat confused how quote unquote works here. Everything I've read about unquote is that it causes the next sexp to be evaluated, which clearly isn't happening here.


macroexpand-1 gives you an idea of what's going to be evaluated. Here it is with the version that I wrote because I think the gensyms make the other versions hard to read:

user=> (macroexpand-1 '(testmacro (+ 1 2)))
{:val (+ 1 2), :form (quote (+ 1 2))}
When {:val (+ 1 2), :form (quote (+ 1 2))} is evaluated at the REPL (+ 1 2) turns into 3, and (quote (+ 1 2)) is (+ 1 2). It helps to separate in your mind the stages of things that happen: macro expansion before evaluation of the resulting form at the REPL.

Cas Shun15:06:23

ok, so We can think of the tilde (~) as saying that we bounce outside the surrounding syntax-quoted form and evaluate the following form in that context, inserting the result back where the tilde was. makes sense to me. I've read a few things about ~ and it seems to often be explained as 'causes to be evaluated'

Cas Shun15:06:24

not sure I fully get the difference between macro expansion time and evaluation time yet though


the phases are (kinda) (eval (macroexpand (read form)))

Suni Masuno15:06:25

Is it possible to have both deeper namespaces and other things within a namespace? Is it a good idea? aka myproj.utils/some-function AND myproj.utils.some-ns/some-other-function


there's no hierarchical relation in clojure namespaces


a.b.c is not "part" of a.b

Suni Masuno15:06:47

So the appearance of a hierarchy is convention?


the filesystem is hierarchical and namespaces follow the filesystem, but that's where it ends

Suni Masuno15:06:23

Neato. ^_^ Now the hard question... is it a good idea? Is that something that is common in the community? Will people be able to brain it?

Cas Shun15:06:26

@bronsa but in my case above, I don't fully understand why '~ is making it an expansion phase value instead of eval.


@suni_masuno it's perfectly fine

👍 4

@auroraminor think of it as a templating system


in some other languages you've probably used string interpolation syntaxes like (i'm making it up) "here's my text $(calculate value)"


it's the same in syntax-quote and unquote (~), just lifted to data

Cas Shun15:06:03

clojure is my first language... almost seeming like a poor choice.


the number of times i have written a macro is 6 years of clojure is exactly once


and even then it was probably a mistake


not a bad first choice @auroraminor

Cas Shun15:06:48

It's been a long journey to even get code working... so many tools


I would love to hear your merciless feedback about tooling

Cas Shun15:06:50

figuring out what to use, how to edit code, the various types of repls...

Cas Shun15:06:53

it's all extremely confusing.


have you been using lein templates to get your projects started or doing it by hand?

Cas Shun15:06:05

a lot of the documentation is very poor at describing "This is a problem", "This is a solution", "This is how you use it"

Cas Shun15:06:27

I've been using lein, because that seems to be the best 'push a button' method


clojure tends to be peoples second or beyond language and the documentation definitely reflects that in places


definitely don't bother with macros for now then :) they're very rarely used

💯 4

clojure has been lots of people's first language, there's a lot of good introductory material out there

Cas Shun15:06:06

yeah, I've read a few books now. Lots of reiteration before anything makes sense


I would love to hear your merciless feedback about tooling

Suni Masuno15:06:18

@auroraminor as someone who jump around languages professionally let me tell you... they're all like that these days. You get 3 options really 1) tons of tools you have to put together yourself (confusing) 2) one god tool everyone has to use or else, which is short all the features you actually want (powerless) 3) no tools... do everything extremely manually or write it your own dang self (effortful)

Cas Shun15:06:08

I would not mind 3 if there was documentation on how things worked

Cas Shun15:06:21

clojure seems like 1 to me, so far

Suni Masuno15:06:46

Yeah, but you can turn 1 into 3

Suni Masuno15:06:51

java -cp .:<PATH>/clojure-1.6.0.jar:./src clojure.main use ONLY this

Suni Masuno15:06:06

do everything else by hand, and read the language spec. Boom, 1->3

Cas Shun15:06:43

except to the previous conversation, there is no language spec... and not even unquote is officially documented it seems?

Suni Masuno15:06:15

that's where I've been living

Cas Shun15:06:22

right now I feel frustrated that I have to go read blogs about how a core language feature is supposed to work

Suni Masuno15:06:56

If you're a #3 person, you can go direct to source. I've done it a few times in other languages.

Cas Shun15:06:36

@suni_masuno that requires quite a bit of bootstrapping I think? In order to learn how to use the language, I need to know how to use the language?

Suni Masuno15:06:43

I was early on some of the JS frameworks and that was all we had.


I think there’s different kinds of documentation. A lot of the details of Clojure are documented perfectly fine as a reference (e.g. you know what you’re looking for and you’re experienced with the language), but not great as an introduction to some of the ideas it uses


which is frustrating at times

Cas Shun15:06:31

yes, that's how I feel

Cas Shun15:06:44

when I know what I need it's usually not too difficult to find information

Cas Shun15:06:50

when I have no clue, it's very difficult

Cas Shun15:06:14

compared to java, which I've started learning recently, it's very easy to figure out out what I need to know AND what I know I need to know


yep. I hear you

Cas Shun15:06:45

browsing various things for python and javascript, they also seem to have lots of official documentation on HOW to do things

Cas Shun15:06:37

I almost feel like I know javascript better than clojure just trying to mess with VSCode to use clojure with. (I'm using emacs now)

Cas Shun15:06:45

er, typescript I guess

Suni Masuno15:06:47

It's more of a target demographic problem. it can be very hard to find anything high level in those languages, because the vast majority of the resources are being made for first year devs. While here it's hard to find things for first year devs, but easy to find things for 10th year devs (which are HARD to find over there) But really those are community measurements, not language.

Suni Masuno15:06:41

If I had a dollar for every "this is what a variable is" paragraph that was between me and a real answer in the official blogs in JS... >.>

Cas Shun15:06:17

I'd be ok with that. It's easier to scan over a paragraph than to be unable to find an answer at all.


more troubling is the number of blog posts / guides in JS that are wrong. but that’s neither here-nor-there

Suni Masuno15:06:09

Fair. The real thing here is people write for themselves (whether they intend to or not) so that just means that's who's in the community. If you join us, and write, you'll bring the missing voice.


I recognize the problem you’re talking about @auroraminor. JavaScript, Java, Python have put a ton of resources into their onboarding documentation and it shows

Suni Masuno15:06:38

But we can't become who we once were (despite how much happier I'd be about my waistline if I could -_-)


@auroraminor can you point me to a particularly helpful official documentation in python or some other language?


(not challenging, but want an example of what would help you)


it’s also helpful to have someone in your immediate vicinity who is good at the language, which is usually so at school or friends when using JS, Python, Java, not so much for Clojure unless you’re lucky

Cas Shun15:06:56

@dpsutton I've read through the official python docs, and I feel they do a good job of explaining what something is, how to use it, and why

Suni Masuno15:06:03

(side note, really great point that is transparent to a lot of us who can't have your perspective! ^_^)

Cas Shun15:06:22 helps with what (sometimes) and how... but almost never why

Cas Shun15:06:36

and often the examples are really obtuse

Suni Masuno15:06:53

Yeah, python was written with the official stated intent that you should be able to learn it by "Reading the docs like a novel" which it's seeming more and more every year was a freak'n great idea.

Cas Shun15:06:12

and while I understand that clojure rarely breaks things, relying on a community effort seems like it could be error-prone

Cas Shun15:06:48

I assume it gets docs directly from a specific version, but who knows if the examples are any good, relevant or idiomatic?

Cas Shun15:06:15

I had an example of that yesterday, but I can't remember what it was. A core function I was trying to use and all of the examples were silly complex

Suni Masuno16:06:27

If doc quality is a primary way you learn python and haskel are likely your favs then. They focus greatly on that. While that isn't so much a motivator for me, I do understand it. Or you could write-as-you-learn and fix the problem. ^_^ but that's a lot of time.

Cas Shun16:06:44

reduce is a good example maybe

Cas Shun16:06:31

the docstring was confusing to me about what f should accept and the order, and all of the examples on were confusing to me


haskell is actually terrible. worse than Clojure IME

Cas Shun16:06:52

this was a few months ago, but I remember being really frustrated by it

Cas Shun16:06:00

because I understood what reduce is, but not how to use it


yes, the docstring for anything that takes a function is really opaque in Clojure IMO


I know it’s a stylistic thing for Rich, but seriously: give me a literal example in the docstring


I’ve just memorized that the arguments for reduce are (fn [accumulator current] ...)


but it’s hard to know how many arguments my function should take when passing it in, what order they are


when reading it written in long-form

Cas Shun16:06:13

if somewhere it said 'the first to f argument is...'

Cas Shun16:06:16

I'd have got it in seconds


anyway this is getting ranty 😛 but just wanted to let you know you are heard, @auroraminor


IMO the payoff of Clojure is pretty great, because once you’re familiar with it, other languages will follow quickly

Suni Masuno16:06:13

Is that the actual code from which the API is generated? And would a PR there actually address (that specific) issue?


but if you’re looking for greatest leverage soonest, I think Python and JS are also a great choice

Cas Shun16:06:23

that is why I chose clojure

Cas Shun16:06:44

I did a lot of research and saw that lisps and functional languages make it easier to understand common concepts

Cas Shun16:06:52

so I chose a functional lisp that seemed practical

Cas Shun16:06:01

which left basically just clojure and sbcl


It's interesting to me that you'd put SBCL over Racket as far as practicality goes.


Not arguing. Would just be curious to hear your thinking there.


uh? that's the common position really

Cas Shun16:06:45

I tried racket for a few days, and when I started researching ahead of myself on how to do something like draw a UI or use common databases... it seemed surprisingly difficult

Cas Shun16:06:50

and clojure seemed very simple


common lisp has always been a massively pragmatic language, racket is a great language but it's definitely suffered a bit in the past due to its pedagogical/academic background, and lacking many real-world libraries


> uh? that's the common position really News to me, and if so I'm not sure I agree. 🙂


Thanks, Cas.

Cas Shun16:06:16

@bronsa that was exactly what I found after a day of non-stop googling. "Ok, that's cool. So how do I draw a window with that information"

Cas Shun16:06:27

and racket seemed difficult at that point


I mean, racket was originally created as a pedagogical language (back when it was called PLT scheme), not as a practical language, that's not really an opinion


common lisp on the other hand was created out of industry needs


I'm aware of Racket's background.

Cas Shun16:06:10

I think I would have gone with some sort of common lisp, but I couldn't get anything working!


I think racket is a great language and things have been improving quickly but the common lisp ecosystem is still massively more vast than the racket one


both in terms of libraries, maturity and community

Cas Shun16:06:48

and it was implied that you were definitely using emacs, which I wasn't, and that was another layer of confusion

Cas Shun16:06:56

ironically, I'm happily using emacs now though


SBCL is widely used in industry compared to racket


and has had many many many more man hours been poured into it


You're pretty passionate about SBCL.


not particularly

Cas Shun16:06:46

@U050CT4HR did you learn/use/attempt/mess with racket?


Well, yes. As a teaching assistant I used to use Racket to teach undergraduates how to program.

Cas Shun16:06:12

do you feel that it has value over clojure in that capacity?


it's a great language at that


Emphatically yes.

Cas Shun16:06:00

why racket over clojure in that capacity?


I'm also of the opinion that Racket has made great strides in the last 10 years or so when it comes to "practicality", but because of its history as a pedagogical language sometimes those advances get overlooked.


> why racket over clojure in that capacity? There are a few different reasons, but one of the biggest ones in my view is the notion of teaching languages.

Cas Shun16:06:18

can you elaborate? I'm very interested in this


Racket isn't just a programming language, it's a system for creating other programming languages. The people behind Racket used it to create a set of "teaching languages" that start small and introduce concepts incrementally.


the docs for racket are also astoundingly good

🙂 4

Also DrRacket is a great IDE to learn to program in. There are lots of reasons.


it's fair to say though that if you want to learn a language you can realistically find a job for, racket isn't really what you should be learning


nor is common lisp i'm sad to say, although some may disagree


No doubt there are fewer Racket jobs.


"fewer" is an understatement for a few orders of magnitude less




I think the answer to the question of which language one should start with has a lot to do with how much pressure one feels to find a job quickly.




if there's no pressure for finding a job, racket is massively valuable and an incredible introduction


One can then branch out and learn other languages, and doing so will be easier because of the strong foundation that "How to Design Programs" provides.


But that only works if you've got the time.

Cas Shun16:06:32

@U050CT4HR did you follow the official racket tutorials much in your TA work?

Cas Shun16:06:40

or work off a separate curriculum?


We taught "How to Design Programs" in particular.


I will say that despite its origins Racket has made significant strides in the area of practicality over the last decade, and that the Racket community has been struggling to get the messaging out about those advances.


In fact, my understanding is that the change of names from "PLT Scheme" to "Racket" was in part an attempt to fix their branding problem.


Leaving aside whether or not it's accurate the perception persists, as is evidenced by Nicola's comments. 😅

Cas Shun16:06:14

one of those things

Cas Shun16:06:17

yeah, I understand that now


the arity without the initial value needs to go away. then it gets much clearer

Cas Shun16:06:38

but the idea that 'applying' implies the order is not obvious to me at all (and I'm a technical writer by trade)


2 arg reduce should be a lint error 🙂

Cas Shun16:06:40

there's quite a few examples like that where the docstring makes no sense to me, and the examples on clojuredocs make it even more confusing

Cas Shun16:06:01

I feel like unquote is a great example. no docstring AND no examples

Cas Shun16:06:11

and yet it's clearly an integral and important part of the language

Suni Masuno16:06:21

I feel like no one is arguing against you so... where ya going with this brah?

Cas Shun16:06:44

@suni_masuno not all conversations have to be arguments


If you want examples, is in many cases better than the built-in doc strings. And you can easily add more with a free account if you wish.

Suni Masuno16:06:07

Yeah, agreed. Still not adverse so... what does it add up to?

Cas Shun16:06:38

@suni_masuno I don't know, but I've already learned some things during this conversation by just chatting. So it's had value to me

Cas Shun16:06:16

@andy.fingerhut thank you for your plethora of examples there, however it's difficult to feel confident enough to add an example when I don't feel like I understand something.


Understood. You could propose something here and get feedback from others to improve it.

Suni Masuno16:06:21

Is there perhaps a pattern in what is sub-optimal in the docs? Something that could be improved?

Cas Shun16:06:50

@suni_masuno I've been browsing for that during this conversation

Cas Shun16:06:05

I think one thing is that almost anything where an argument is a function, seems to be opaque


If you find that you want to replace all doc strings, not just a few, then you will likely find that creating your own, perhaps through collaboration on a site like, or some other means, is going to be less frustrating than attempting to get the official doc strings to change.

Cas Shun16:06:36

also, clojure apparently has idioms for argument names, but they aren't explicitly discussed anywhere I can see?

Cas Shun16:06:40

f n x xs etc...

Cas Shun16:06:54

I know what these are after many months now, but when I was trying to learn it was frustrating

Suni Masuno16:06:08

Interesting, so if perhaps the clojuredocs page hyperlinked the args to descriptions of their shorthand it would short circuit a problem?

Cas Shun16:06:11

clojure.spec is a good one. I understand the talks I think, but when I tried to read the docs, I feel like I really have no clue.

Suni Masuno16:06:51

I'm not familiar with the way clojuredocs is implemented, but I've seen similar features in project docs in other languages using a regex match for common key phrases getting links.


Yeah, arg name conventions would be nice to have described somewhere "central", but not sure yet where a good place would be...


Perhaps a new 'guide' article to add to the existing ones here?

Cas Shun16:06:36

a glossary of some sort would be nice

Cas Shun16:06:57

like the 'reading clojure characters' article, that's helped me SO much because it's basically a glossary of common terms


For 'spec' docs in particular, enhancing the existing guide is probably the best way to go:

Cas Shun16:06:36

I wouldn't even know where to start, except that the spec guide basically de-learns what I feel like I learned by watching talks about spec 😞

Cas Shun16:06:50

Excellent, thank you. I still feel like I have massive holes in my fundamental understanding of the language. This looks great

Cas Shun16:06:18

I made the 'mistake' of reading "Programming Clojure" first, and multiple times since. I still don't understand probably 75% of that book.

Cas Shun16:06:03

@deleted-user the thing that's tripping me up, I think, is figuring out what happens at macroexpansion, and what gets eval'd. Like why can't I just use x from the defmacro, is it not 'in scope'?

Suni Masuno16:06:29

macroexpand-1 was the trick for me, playing with ideas in a repl

Cas Shun16:06:02

I used macroexpand-1 for a few hours before asking here 😞

Cas Shun16:06:13

I knew what was wrong, but not how to fix it

Cas Shun16:06:36

now I know how to fix it, but not totally clear on why it works

Cas Shun16:06:27

trying some things at repl now

Cas Shun16:06:15

(defmacro atest [x]
  `(let [g# '~x]

(defmacro btest [x]

(macroexpand-1 (atest y))
;; => y
(macroexpand-1 (btest y))
;; => Wrong number of args (0) passed to: clojure.lang.Symbol

Cas Shun16:06:43

so I'm unclear on why it works with let and gensym, but not just by itself?

Cas Shun16:06:11

I would think that btest would give (y)?

Cas Shun16:06:25

or (quote y)?

Cas Shun16:06:59

ok, that works


Try (macroexpand-1 '(btest y))

Cas Shun16:06:20

but doesn't ` quote what's inside?

Cas Shun16:06:29

I don't understand why it's trying to run it as a function

Cas Shun16:06:55

ok now I'm even MORE confused...


It expects an expression to be macroexpanded. If you do not quote it, it will be eval'd, and the result will be passed to macroexpand-1


macroexpand-1 is a function, so all of its arguments are eval'd before the function is called.

Cas Shun17:06:58

so if I quote what I send to macroexpand-1, btest does give (quote y) like I expected, but atest is (clojure.core/let [g__21536__auto__ 'y] g__21536__auto__)


When I do it, (macroexpand-1 '(btest y)) gives ((quote y)), which is not the same as (quote y)

Cas Shun17:06:07

@andy.fingerhut that's correct, poor paste by me.


Parentheses are not optional things you can just add on or remove in Clojure, without changing the meaning of an expression, the way they can be in some other programming languages.


So (macroexpand-1 '(atest y)) does expand to (clojure.core/let [g__2__auto__ (quote y)] g__2__auto__) when I eval that in a REPL. The only reason you see different numbers inside the g__2__auto__ symbol name is because you have been running that REPL much longer than I have mine. The just get auto-numbered names.

Cas Shun17:06:16

yes, I understand the gensym thing (hopefully)


(clojure.core/let [g__2__auto__ (quote y)] g__2__auto__) means exactly the same thing as (clojure.core/let [x (quote y)] x). It just has a different name than x that has been auto-generated while back-quoted expression was being read.

Cas Shun17:06:09


(defmacro btest [x]

(defmacro ctest [x]

(macroexpand-1 '(btest y))
;; => y
(macroexpand-1 '(ctest y))
;; => x

Cas Shun17:06:30

I believe I understand ctest. I'm quoting x, I get x.

Cas Shun17:06:03

btest... is `~ what allows me to get x from the defmacro bindings?

Cas Shun17:06:17

is there another way to get that x's value that's passed to the macro?


user=> (defmacro dtest [x] x)
user=> (macroexpand-1 '(dtest y))


One way to think of backquoted expressions is like a little "template". You don't have to use backquoted expressions ever when defining a macro, or at any other time. You can instead write Clojure code inside of a macro definition that "builds" and returns the expression you want.


I had this issue with CIDER lately - when I use lein on jack-in I don't start in the main namespace of the lein project and the user-ns has no clojure.repl-ns preloaded. I was told this should not be so, but I wanted to be sure, so could someone tell me whether this is indeed unexpected behaviour?


come to #cider

👍 4

Every macro that can be written with backquote can be written without backquote.


The backquoted expressions are a convenience that are useful for many kinds of macros, which can make it easier to write those macros.

Cas Shun17:06:52

@andy.fingerhut my original example was

(defmacro testmacro [x]
 `(let [form# '~x
        val# ~x]
    {:val val# :form form#}))

Cas Shun17:06:02

where I was confused why the '~ was necessary


It isn't "necessary". You could write that entire macro without using backquote at all -- it would just be a bit harder to write and read.


I mean, I'm being a bit pedantic there. But there is nothing about Clojure macros that requires you to use backquote at all.

Cas Shun17:06:32

pedantic is nice, it's what I need


If you do use a backquote expression, then the rules are that every symbol inside of that backquoted expression is resolved into its fully qualified version, with a namespace. Every symbol that doesn't have ' ` ~ stuff in front of it that is.


So if you have this template, and you don't want the symbol to just become fully namespace qualified and keep its name, you need a syntax to inform the Clojure reader to do something different with it.


So, let me look at your code example then, where I guess the question is why does it seem like 'x is needed in one place, but x is good enough in a different place?

Cas Shun17:06:53

@andy.fingerhut the contrary is without the syntax quote

(defmacro testmacro [x]
  (let [form# x
        val# ???]
    {:val val# :form form#}))
I was here originally and didn't understand how to evaluate x to get the eval'd value of it.


You want a macro such that you can write (testmacro some-expression) in your code, and it is transformed, before the compiler gets a crack at it, into {:val some-expression :form (quote some-expression)} ?

Cas Shun17:06:42

yes, that is correct. I was just writing that.

Cas Shun17:06:48

sorry for my unclearness of what I want to happen


Here is one way that doesn't use backquote at all:


user=> (defmacro tmacro [expr]
{:val expr :form (list 'quote expr)})
user=> (macroexpand-1 '(tmacro (+ 5 7)))
{:val (+ 5 7), :form (quote (+ 5 7))}
user=> (tmacro (+ 5 7))
{:val 12, :form (+ 5 7)}


I suspect most people would use a backquoted expression more commonly for this. I just wanted to demonstrate that it wasn't needed.


This variation also does not use backquote at all:


user=> (defmacro tmacro2 [expr]
(let [form (list 'quote expr)
      val expr]
  {:val val :form form}))
user=> (macroexpand-1 '(tmacro2 (+ 5 7)))
{:val (+ 5 7), :form (quote (+ 5 7))}
user=> (tmacro2 (+ 5 7))
{:val 12, :form (+ 5 7)}

Cas Shun17:06:59

hmm, let me try


This is about the simplest version I can think of that does use backquote:

Cas Shun17:06:27

that (list 'quot part is throwing me a bit


user=> (defmacro tmacro3 [expr]
`{:val ~expr :form '~expr})
user=> (macroexpand-1 '(tmacro3 (+ 5 7)))
{:val (+ 5 7), :form (quote (+ 5 7))}
user=> (tmacro3 (+ 5 7))
{:val 12, :form (+ 5 7)}


If you have a macro body that has no backquoted expressions in it, it is a Clojure function that takes an unevaluated expression as input, and returns an unevaluated expression as output, that will then be evaluated by the compiler as if you had typed it that way.


user=> (def expr '(+ 5 7))
user=> (list 'quote expr)
(quote (+ 5 7))


Not sure if I am making your brain hurt more, or less, right now 🙂

Cas Shun17:06:48

you're making it work at least

Cas Shun17:06:32

@andy.fingerhut so in your example, how would I get the evaluation of the expr without using backquote?

Cas Shun17:06:02

I am curious if there is a solution besides (eval)


Does tmacro3 not do what you ask?


Sorry I meant tmacro1


Grr. This one:

user=> (defmacro tmacro [expr]
{:val expr :form (list 'quote expr)})
user=> (macroexpand-1 '(tmacro (+ 5 7)))
{:val (+ 5 7), :form (quote (+ 5 7))}
user=> (tmacro (+ 5 7))
{:val 12, :form (+ 5 7)}

Cas Shun17:06:46

yes... hold on


the evaluated (+ 5 7), or 12, is in the return value of the last expression

Cas Shun17:06:26

I was looking at the output of macroexpand, not using the macro 🙂

Cas Shun17:06:08

ok, I think I understand this now... until I try to write another macro I'm sure 🙂


backquoted expressions still confuse me, when I see combinations of ' ~ etc in front of symbols inside of those backquoted expressions. I don't use them that often, because I don't use macros that often.

Cas Shun17:06:43

the backquote (syntax quote?) and unquote seem to be poorly explained in various sources

Cas Shun17:06:51

and from what I can see, there's no official docs at all


Your example seems to be a place where a macro is actually necessary, since you want to preserve an expression unevaluated in the result, so you are not reaching for one frivolously here.

Cas Shun17:06:42

and when most of the macros that I tried to read the source of use various combinations of holding shift and face-rolling the number row...

Cas Shun17:06:46

it's getting super confusing


there's some great material on syntax quote in the common lisp book "on lisp", which applies to clojure just by replacing , with ~ (the first is the common lisp version of unquote) if you're looking for some good explanations


one more way to learn how this works is to realize that syntax-quote is merely sugar over list manipulating functions and quote, and try to figure out what each expression would correspond to

Cas Shun17:06:09

I feel like I'm past wanting good explanations, and I just want good documentation 😞 I will pick up the book though, thank you.


for example:

`(foo ~@(bar baz))
is equivalent to
(concat (list (quote foo))) (list bar baz))


as a first step I'd suggest you try implementing your macros w/o using syntax-quote at all

Cas Shun17:06:29

well, that's what I would have done if there were any examples of people doing that


just as normal list manipulations


I'd also like to remind you my previous suggestion to ignore macros alltogether until you're comfortable writing normal clojure :) macros really aren't used that much at all


they're advanced features that most users of clojure will only ever need to implement once or twice in years

Cas Shun17:06:49

I did think that my use for a macro here was ok, since I wanted the actual expression and its evaluated value

Cas Shun17:06:55

is that incorrect?


no it's perfectly fine

Cas Shun17:06:17

I did google around for misuse of macros because I heard that most things can be done with functions


but are you trying to do this because of actual need or just as an exercise?

Cas Shun17:06:26

I think it's an actual need. I was writing a macro to wrap cognitect.rebl/submit, which wants the expression and value. I also wanted to add some extra output for myself

Cas Shun17:06:57

I have no clue why I'm using REBL to begin with... it just seemed neat


ok, then what I like to remind people approaching macros for the first time is that: 1- syntax quote is not necessary to write macros 2- there's nothing "special" about syntax-quote and its usage in macros, it's just sugar for creating lisp values that can be done exactly the same using normal list manipulating functions 3- macros are literally normal functions that take as input a list expression and return a list expression, they're simply executed at an earlier stage than normal functions

👍 4

with that in mind, you should first try to implement your macro as a normal function over a quoted list


not using syntax-quote


if you're not able to do that yet, that's a sign that you're maybe trying to run before you can walk, and I'd recommend focussing on getting comfortable with manipulating lists and normal values, since that's really what you're trying to do


does that make sense?

Cas Shun17:06:33

yes, absolutely

Cas Shun17:06:58

I would have much preferred to start that way in fact, but I was working based off the articles/examples I found

Cas Shun17:06:22

which all made it seem like there was a macro-specific sub-language to be used


there's no such thing, syntax-quote can be used in normal functions, it just so happens that it's mainly used to implement macros

Cas Shun17:06:20

yes, I figured that out somewhere in the middle of this 🙂


(my phrase may be considered a bit disingenuous, it's undoubtly true that syntax-quote would not exist if it wasn't for helping writing macros, but that doesn't mean it's its purpose or that it has any special realationship with macros)

Cas Shun17:06:34

it does seem strange to me that we work with sequences constantly using these functions, but when writing a macro you're idiomatically expected to use a different set of tools which correspond to the normal tooling anyway


well, it's just more concise

Cas Shun18:06:10

we're not writing things with pencils though 😉


writing complex macros w/o using syntax-quote would result in code orders of magnitude harder to parse for a human


even if the resulting code would be absolutely identical to the compiler

Cas Shun18:06:04

I would need to encounter those to fully understand I think

Cas Shun18:06:20

or just find some core macros and try to de-quotify them


yep. I’m writing a macro right this second, that is very readable using syntax-quote:

(defmacro defnc
  "Defines a new React component."
  [display-name props-bindings & body]
  (let [usables (find-all hook?
       (defn ~display-name [props#]
         (let [~props-bindings [(->props props#)]] ;; `props` is a proxy to `bean`
       (signature ~display-name (list ~@(map str usables)))
       (register ~display-name ~(str display-name)))))


a fun trick: sticking a ' in front of a syntax-quoted expression will show you what it desugars to



`(foo ~@[1 2] '~3)
for example


user=> '`(foo ~@[1 2] '~3)
(clojure.core/seq (clojure.core/concat (clojure.core/list (quote user/foo)) [1 2] (clojure.core/list (clojure.core/seq (clojure.core/concat (clojure.core/list (quote quote)) (clojure.core/list 3))))))


the same effect can be obtained by using read-string on the string representation


user=> (read-string "`(foo ~@[1 2] '~3)")
(clojure.core/seq (clojure.core/concat (clojure.core/list (quote user/foo)) [1 2] (clojure.core/list (clojure.core/seq (clojure.core/concat (clojure.core/list (quote quote)) (clojure.core/list 3))))))


understanding why this works may be a bit too complex for now, but nonetheless it could be a good way for you to see what a syntax quoted expression expands to and get an understanding of how syntax-quote and unquote work

Cas Shun18:06:54

excellent, that is very helpful thank you

Drew Verlee21:06:44

will a timeout expring on a core async thread cause the work being done on that thread to stop?


a timeout is a channel


your question doesn't really make sense


a timeout is a channel that closes after a certain amount of time has passed, it has nothing what so ever to do with threads

Drew Verlee21:06:39

right, yea. I got my wires crossed for a second. The timeout applies to the channel.


a timeout is a channel


it is not something that applies to a channel

Drew Verlee21:06:18

i'm now confused about you mean


the docstring says it


a timeout is a channel

Drew Verlee21:06:39

it doesnt say that though


Returns a channel that will close after msecs


you can imagine timeout as being something like (defn timeout [ms] (let [c (chan)] (future (Thread/sleep ms) (async/close! c)) c))


it is a channel, not an operation on channels, or something you can apply to a channel


sorry, left out the returning the channel


it isn't implemented like that because it tries to optimize the timeouts and limit the number of channels created, but that is the naive implementation


so if you have something like (alt! (timeout 100) ([...] ...) C ([...] ...)) that isn't a timeout on the channel C, that is two channel operations (a read from the timeout channel or a read from C) being chosen between


so replace the timeout channel with X, and ask your self, if alts chooses to read from X, why would that do anything to C


and then the further question is, given C is a channel, why would anything happening to C stop a thread from executing


channels are not futures

Drew Verlee21:06:52

hmm ok. After reading this and reviewing core async, i'm probably looking at the wrong tool. I want to spawn a couple separate threads and have them do work but have a way for them to short circuit after some amount of time. Futures will place the body on another thread. I suppose stopping the work is something that has to be custom built? Like i can build a while loop that stops after a number of iterations or after some amount of time passes. There is nothing that generically wraps some body of code evals it tell some amount of time.


future-cancel works if the future in question spends time waiting on io or sleeping


you would do that from another thread


there are task scheduling tools like executors that come with the vm as well


most poetic doc string in clojure.core

(ins)user=> (doc future-cancel)
  Cancels the future, if possible.


And there are warnings about using the underlying Java cancel method, and/or attempting to forcefully stop a thread, etc, that are easy to find via Google searches. Having some thread/task/whatever periodically check whether some "master" wants it to stop doing its work, enables you to write code so that thread/etc. stops cleanly, with the tradeoff that infinite or long-running loops in your code between such checks will increase how long of a time can elapse before it stops doing work.


yeah - but in my experience it's very common for long running tasks that you might want to cancel to either sleep or wait on IO


and those are both things that make a task cancellable


another pattern is to use a promise or delay as a one time cancellation switch, exiting a loop if it is realized


Is there a need for shutdown-agents if you already have a System/exit in -main?


yes, the agent pool can still delay exit or maybe not


@noisesmith I'm not using any agents but I guess some lib might? or is this uncommon


it's more common to use the agent thread pool via future actually


future uses the send-off pool


Oh ok, ithe thread pool for agents is "shared"


yeah - I don't know if anything but future uses that pool, but future is relatively commonly used


in library code?


no, not in library code


future is called by and pmap inside of Clojure, so they have similar effect of causing a 60-second delay before a process actually exits if you do not call shutdown-agents or System/exit:

👍 8