This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-03-25
Channels
- # announcements (2)
- # beginners (30)
- # biff (12)
- # calva (2)
- # cider (62)
- # clerk (18)
- # clj-commons (20)
- # clojure (9)
- # clojure-europe (6)
- # clojure-switzerland (1)
- # core-logic (6)
- # datomic (4)
- # events (3)
- # fulcro (1)
- # membrane (15)
- # off-topic (16)
- # portal (24)
- # practicalli (2)
- # releases (1)
- # rewrite-clj (45)
- # shadow-cljs (14)
- # tools-build (1)
- # xtdb (4)
As part of my work overhauling https://clojure-doc.org this year, I have rewritten the Basic Web Development guide to show how to build a simple (server-side) web application from scratch using the Clojure CLI, deps.edn
, and build.clj
: https://clojure-doc.org/articles/tutorials/basic_web_development/ -- I would love to gather feedback from beginners on this, if there's anything you cannot follow, or you think is confusing, please let me know either on this discussion https://github.com/clojure-doc/clojure-doc.github.io/discussions/41 or as a thread here. Thank you!
It assumes you have the Clojure CLI already installed and can work at the command-line, even on Windows (in particular, if there are changes or additions that make this easier to follow on Windows, I would really appreciate that!).



Nice simple intro. Thanks.
I noticed several code blocks state the incorrect file name, ;; src/my_app/
, which should be ;; src/my_webapp/
(I assume that was the name in the older example before the update)
looks awesome!
I think it looks great at a glance. I'm not a beginner, but I very much like how you structured it, because the code is so straight forward. There is no abstraction or mental model that a reader needs to follow. Just basic stuff without surprises that goes straight from A to Z. I might actually use this to teach someone Clojure web dev.
@U05254DQM Good catch -- thanks! Fixed.
Thanks, folks! The original text and code were both by John Gabriele, who hasn't been active here since 2017, but contributed a lot of great material on http://clojure-doc.org -- I just reorganized it and brought the code up-to-date (and switched it from Leiningen to Clojure CLI). Thanks, folks. The original work was all by John Gabriel
Very nice. This is the kind of thing I was looking for when I was a beginner, but I got lost in the amount of choices available. This kind of clear tutorial should be one of the first entry points for beginners.
Hey guys.
17 | (let [list [::labour ::material ::other]]
18 | (doseq [name list] (s/def name price-validator))
19 | (s/def ::cost-of-sale (s/keys :opt-un list)))
-------------------------------^---------------------------
Encountered error when macroexpanding cljs.spec.alpha/keys.
IllegalArgumentException: Don't know how to create ISeq from: clojure.lang.Symbol
I can surely use variables in macros, can't I? What's the problem here?No spec expert, but the example I see suggests you need a vector round the s/keys args:
(s/def ::user (s/keys :req-un [::id ::name]))
Yes, I used to have just that, but then I extracted the vector as list (in let), because I want to make that iteration over it and then pass it to the s/keys
macros.
Of course it does work with (s/keys :opt-un [])
, but I'm surprised it doesn't work when I replace []
by a variable.
The error is not saying you cannot use variables in a macro, it is saying that, while macroexpanding your form it hit an error where it wanted an ISeq
but got a clojure.lang.Symbol
.
I'm not following. Like OK, the symbol is the list
variable, I get that, but why shouldn't it be there?
"I'm surprised it doesn't work when I replace []
by a variable."
Heh-heh, well the author of the macro chose to require a vector right there. I have written code that gracefully tolerates an atom or a sequence and works it out on the fly, but not always. Pardon, by the way, if my ignorance of spec is getting in the way.
Saw a bit more:
(s/keys :req-un [:my.ns/x :my.ns/y])
It is just an API choice.
btw, what are we expecting list
to do in that position? Produce []
?Ima gonna be quiet, I think I do not know enough about spec!
Very simple, I had this:
(s/def ::labour price-validator)
(s/def ::material price-validator)
(s/def ::other price-validator)
(s/def ::cost-of-sale (s/keys :opt-un [::labour ::material ::other]))
It worked, but I kept forgetting to add a new item into the vector and then got errors.
So I said f* it, let's have the vector as a variable:
(let [list [::labour ::material ::other]]
(doseq [name list] (s/def name price-validator))
(s/def ::cost-of-sale (s/keys :opt-un list)))
Simply to replace the previous version. It's very basic really even if you know nothing about spec, essentially my question/surprise is:
Given that spec takes this:
(s/def ::cost-of-sale (s/keys :opt-un [::labour ::material ::other]))
How the hell does it not work with the vector replaced by a reference?
(s/def ::cost-of-sale (s/keys :opt-un list))
It's what one would never guess could NOT work. I get it's a macro, but still...Oh, my, yes this is indeed a macro thing. Macros may or may not evaluate an arg. Your usage is hoping for evaluation, which indeed sometimes will be the case, but in this case the macro expansion code wants a vector in the code. Not at runtime when evaluation would take place.
Yeah it makes sense, but it surprised me anyways 🙈
Solution: write another macro that unpacks it 😄 I suppose is the way there. I think I'll just go with spec and will remember to update the list.
If you want to have fun, create a toy macro and outside any backquote expansion just print the parameters. It can be a downright shock what the Clojure reader presents to the macro.
My favorite was when I coded (mymacro 'foo)
ind the print statement just inside mymacro
showed (quote foo)
.
Oh yes.
OK, anyway at least I know what's going on now.
Just noticed one macro in my Matrix API:
(defmacro cF+ [[& options] & body]
`(make-c-formula
~@options
:code '~body
:value unbound
:rule (c-fn ~@body)))
The very destructuring demands a usage like:
(cF+ [:debug false]
(let [route (mget me :route)]
(some (fn [lesson]
(when (= route (:route lesson))
;; (prn :selected-lesson-checks-lesson (:route lesson) lesson)
lesson)) lessons)))
But a different macro looks at the code it is being asked to expand decides which of three allowed patterns has been coded.
(defmacro ~tag [& ~vargs]
(let [~tag-name (str '~tag)]
(cond
(nil? ~vargs)
`(tiltontec.web-mx.gen/make-tag ~~tag-name {} {} nil)
(map? (first ~vargs))
(cond
(map? (second ~vargs))
....etc....)
"tag" is HTML tag. So that macro-writing macro writes macros that support any of:
(span { :style "color:red"} { :some-custom-state 42} (str "The value is " (mget me :some-custom-state)))
(span { :style "color:red"} (str "The value is " (rand-int 99)))
(span (str "The value is " (rand-int 99)))
The alternative would be forcing the latter case to be coded:
(span {} {} (str "The value is " (rand-int 99)))
That would get old fast."Solution: write another macro that unpacks it 😄 I suppose is the way there." Haha, I missed that ^^^, but saw it when I was about to suggest exactly that. Good exercise in macrology, if nothing else. But I would indeed have offered the same smiley.

I encountered this a while ago, here's a solution (on my phone, so I'm writing this from memory, might have typos; use with caution):
(let [list [::labour ::material ::other]]
(doseq [name list] (s/def name price-validator))
(eval `(s/def ::cost-of-sale (s/keys :opt-un ~list))))
I wouldn't recommend it though. If you need to create specs dynamically like that you'll encounter more issues. I think Malli is more flexible.
Alternatively you could use clojure.spec.alpha/describe
or clojure.spec.alpha/form
and get the vector from the spec definition