This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2019-06-18
Channels
- # aws (12)
- # beginners (489)
- # calva (32)
- # cider (117)
- # clj-kondo (4)
- # cljdoc (9)
- # cljs-dev (3)
- # clojure (56)
- # clojure-brasil (1)
- # clojure-europe (10)
- # clojure-italy (44)
- # clojure-nl (9)
- # clojure-russia (1)
- # clojure-sweden (6)
- # clojure-uk (64)
- # clojurescript (6)
- # cursive (6)
- # datascript (4)
- # datomic (6)
- # emacs (3)
- # fulcro (15)
- # graalvm (11)
- # jackdaw (7)
- # jobs (8)
- # jobs-discuss (29)
- # jvm (2)
- # leiningen (5)
- # luminus (10)
- # off-topic (23)
- # pathom (21)
- # planck (11)
- # quil (6)
- # re-frame (11)
- # reagent (17)
- # reitit (8)
- # rewrite-clj (6)
- # shadow-cljs (78)
- # slack-help (2)
- # spacemacs (7)
- # specter (4)
- # sql (60)
- # tools-deps (7)
- # xtdb (11)
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.
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 https://github.com/luminus-framework/luminus-template#usage
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
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.
A friend/colleague of mine swears by Emacs, but I was much happier when I learned there was vim integration for it.
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...
Have a look at Keechma and Hoplon too.
Heh, we'll see how far I get 🙂
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 🙂
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 🙂
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) 🙂
@seancorfield this might be handy for a quick re-frame intro https://github.com/ClojureTO/JS-Workshop/tree/re-frame
and I can highly recommend shadow-cljs, it makes working with NPM modules completely seamless
@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
🙂Thanks. You may regret that offer 😉
The PDF of your 3rd ed is in my browser for the weekend reading/learning!
... 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?
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? 🙂
The next chapters will focus on stuff like auth, file uploads, packaging, testing, and deployment
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".
(clojure.repl/doc write-secret)
; =>
dal.secretsmanager/write-secret
([secret value])
write-secret writes a secret
Spec
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
thanks
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.
@gerred This is probably still one of the best guides to figuring out types in Clojure https://cemerick.com/2011/07/05/flowchart-for-choosing-the-right-clojure-type-definition-form/
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.
@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).
@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
)user=> (s/def ::direction #{:north :south :east :west})
:user/direction
user=> (s/def ::dir-set (s/coll-of ::direction :kind set? :min-count 0 :max-count 4))
:user/dir-set
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}])
user=>
Because that's how specs work 🙂
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?.
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
"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 https://github.com/clojure/spec-alpha2/wiki/Differences-from-spec.alpha#symbolic-specs 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. 😅
Clojure 1.10.x should run on any Java 8+
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 https://sdkman.io/ for managing jdks.
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. 🙂
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
(see https://github.com/gcuisinier/jenv/issues/44#issuecomment-233124185)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
java11
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
that has wrappers for JS libraries, i’m not sure if it is of any help if you want pure cljs libraries
https://github.com/jkk/verily it looks kinda old
@m373h4n It is ok to use old libraries in ClojureScript. There are rarely breaking changes, if that's your concern.
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?@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.
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)}
@clojurians971 thanks, that does work.
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.Perhaps this is useful for you? https://8thlight.com/blog/colin-jones/2012/05/22/quoting-without-confusion.html
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'
not sure I fully get the difference between macro expansion time and evaluation time yet though
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
So the appearance of a hierarchy is convention?
the filesystem is hierarchical and namespaces follow the filesystem, but that's where it ends
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?
@bronsa but in my case above, I don't fully understand why '~
is making it an expansion phase value instead of eval.
@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)"
not a bad first choice @auroraminor
have you been using lein templates to get your projects started or doing it by hand?
a lot of the documentation is very poor at describing "This is a problem", "This is a solution", "This is how you use it"
clojure tends to be peoples second or beyond language and the documentation definitely reflects that in places
clojure has been lots of people's first language, there's a lot of good introductory material out there
I would love to hear your merciless feedback about tooling
@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)
Yeah, but you can turn 1 into 3
java -cp .:<PATH>/clojure-1.6.0.jar:./src clojure.main
use ONLY this
do everything else by hand, and read the language spec. Boom, 1->3
except to the previous conversation, there is no language spec... and not even unquote is officially documented it seems?
better sources, https://clojure.org/reference/compilation
that's where I've been living
right now I feel frustrated that I have to go read blogs about how a core language feature is supposed to work
If you're a #3 person, you can go direct to source. I've done it a few times in other languages. https://github.com/clojure/clojure/
@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?
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
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
browsing various things for python and javascript, they also seem to have lots of official documentation on HOW to do things
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)
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.
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... >.>
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
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
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?
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
@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
(side note, really great point that is transparent to a lot of us who can't have your perspective! ^_^)
http://clojuredocs.org helps with what (sometimes) and how... but almost never why
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.
and while I understand that clojure rarely breaks things, relying on a community effort seems like it could be error-prone
I assume it gets docs directly from a specific version, but who knows if the examples are any good, relevant or idiomatic?
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
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.
the docstring was confusing to me about what f should accept and the order, and all of the examples on https://clojuredocs.org/clojure.core/reduce were confusing to me
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
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
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
I did a lot of research and saw that lisps and functional languages make it easier to understand common concepts
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
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
@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"
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
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
and it was implied that you were definitely using emacs, which I wasn't, and that was another layer of confusion
@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.
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.
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. https://docs.racket-lang.org/drracket/htdp-langs.html
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
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.
@U050CT4HR did you follow the official racket tutorials much in your TA work?
We taught "How to Design Programs" in particular. https://htdp.org/
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. 😅
but the idea that 'applying' implies the order is not obvious to me at all (and I'm a technical writer by trade)
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
I feel like no one is arguing against you so... where ya going with this brah?
@suni_masuno not all conversations have to be arguments
If you want examples, http://ClojureDocs.org is in many cases better than the built-in doc strings. And you can easily add more with a free account if you wish.
Yeah, agreed. Still not adverse so... what does it add up to?
@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
@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.
Is there perhaps a pattern in what is sub-optimal in the docs? Something that could be improved?
@suni_masuno I've been browsing for that during this conversation
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 http://ClojureDocs.org, or some other means, is going to be less frustrating than attempting to get the official doc strings to change.
also, clojure apparently has idioms for argument names, but they aren't explicitly discussed anywhere I can see?
I know what these are after many months now, but when I was trying to learn it was frustrating
Interesting, so if perhaps the clojuredocs page hyperlinked the args to descriptions of their shorthand it would short circuit a problem?
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.
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? https://clojure.org/guides/getting_started
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: https://clojure.org/guides/spec
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 😞
Regarding idiomatic arg names there's this: https://github.com/bbatsov/clojure-style-guide#idiomatic-names
Excellent, thank you. I still feel like I have massive holes in my fundamental understanding of the language. This looks great
I made the 'mistake' of reading "Programming Clojure" first, and multiple times since. I still don't understand probably 75% of that book.
@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'?
macroexpand-1
was the trick for me, playing with ideas in a repl
(defmacro atest [x]
`(let [g# '~x]
g#))
(defmacro btest [x]
`('~x))
(macroexpand-1 (atest y))
;; => y
(macroexpand-1 (btest y))
;; => Wrong number of args (0) passed to: clojure.lang.Symbol
Try (macroexpand-1 '(btest y))
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.
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)
@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.
(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.
so
(defmacro btest [x]
`~x)
(defmacro ctest [x]
'x)
(macroexpand-1 '(btest y))
;; => y
(macroexpand-1 '(ctest y))
;; => x
user=> (defmacro dtest [x] x)
#'user/dtest
user=> (macroexpand-1 '(dtest y))
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?
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.
@andy.fingerhut my original example was
(defmacro testmacro [x]
`(let [form# '~x
val# ~x]
{:val val# :form form#}))
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.
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?
@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)}
?
Here is one way that doesn't use backquote at all:
user=> (defmacro tmacro [expr]
{:val expr :form (list 'quote expr)})
#'user/tmacro
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/tmacro2
user=> (macroexpand-1 '(tmacro2 (+ 5 7)))
{:val (+ 5 7), :form (quote (+ 5 7))}
user=> (tmacro2 (+ 5 7))
{:val 12, :form (+ 5 7)}
This is about the simplest version I can think of that does use backquote:
user=> (defmacro tmacro3 [expr]
`{:val ~expr :form '~expr})
#'user/tmacro3
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/expr
user=> (list 'quote expr)
(quote (+ 5 7))
Not sure if I am making your brain hurt more, or less, right now 🙂
@andy.fingerhut so in your example, how would I get the evaluation of the expr without using backquote?
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/tmacro
user=> (macroexpand-1 '(tmacro (+ 5 7)))
{:val (+ 5 7), :form (quote (+ 5 7))}
user=> (tmacro (+ 5 7))
{:val 12, :form (+ 5 7)}
the evaluated (+ 5 7)
, or 12, is in the return value of the last expression
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.
the backquote (syntax quote?) and unquote seem to be poorly explained in various sources
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.
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...
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
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
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
I did think that my use for a macro here was ok, since I wanted the actual expression and its evaluated value
I did google around for misuse of macros because I heard that most things can be done with functions
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
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
with that in mind, you should first try to implement your macro as a normal function over a quoted list
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
I would have much preferred to start that way in fact, but I was working based off the articles/examples I found
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
(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)
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
writing complex macros w/o using syntax-quote would result in code orders of magnitude harder to parse for a human
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?
body)]
`(do
(defn ~display-name [props#]
(let [~props-bindings [(->props props#)]] ;; `props` is a proxy to `bean`
~@body))
(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
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))))))
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
will a timeout expring on a core async thread cause the work being done on that thread to stop?
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
right, yea. I got my wires crossed for a second. The timeout applies to the channel.
i'm now confused about you mean
it doesnt say that though
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
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
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)
-------------------------
clojure.core/future-cancel
([f])
Cancels the future, if possible.
nil
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
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
yeah - I don't know if anything but future uses that pool, but future is relatively commonly used
future is called by clojure.java.shell/sh 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
: https://clojuredocs.org/clojure.core/future