This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2021-07-22
Channels
- # announcements (6)
- # babashka (8)
- # beginners (136)
- # cider (5)
- # cljs-dev (1)
- # cljsrn (1)
- # clojure (198)
- # clojure-argentina (4)
- # clojure-australia (1)
- # clojure-europe (25)
- # clojure-italy (4)
- # clojure-nl (5)
- # clojure-poland (1)
- # clojure-spec (4)
- # clojure-uk (4)
- # clojuredesign-podcast (4)
- # clojurescript (36)
- # conjure (11)
- # data-science (1)
- # datomic (6)
- # defnpodcast (1)
- # deps-new (5)
- # emacs (7)
- # events (1)
- # fulcro (10)
- # graalvm (9)
- # graalvm-mobile (10)
- # helix (9)
- # introduce-yourself (1)
- # jackdaw (1)
- # jobs-discuss (5)
- # kaocha (6)
- # lsp (10)
- # malli (11)
- # missionary (28)
- # off-topic (2)
- # pathom (24)
- # pedestal (7)
- # portal (1)
- # re-frame (12)
- # reagent (2)
- # reitit (1)
- # remote-jobs (1)
- # sci (7)
- # shadow-cljs (6)
- # sql (6)
- # tools-deps (10)
- # vim (9)
- # xtdb (19)
I am not sure where to put this.... but has anybody combined the development environment of a server like pedestal and cljs environment like figwheel? How do you do that?
so... say when I launch the fig-wheel .. instead of launching its own server... it launches the pedestal server which will be serving the frontend... and the repl will still be available...
@U01EFUL1A8M I'll check it out... many thanks!
You can do it, at least with shadow-cljs, by just calling the same code your CLJS build tool calls when you use its CLI.
I used to do that, but went back to two separate processes - simply because having the same classpath for backend runtime and for UI compilation doesn't make sense and is incredibly brittle.
my java knowledge has pretty much corroded at this point. I just found I wanted to proxy
an interface that has a default method implementation - which I’d never seen before. Seems that just not implementing the method will not work; the default implementation is then not found. Any way to do this? Specifically, I want to proxy this class: https://bitbucket.org/atlassian/swagger-request-validator/src/master/swagger-request-validator-core/src/main/java/com/atlassian/oai/validator/model/Request.java which has a default getRequestBody
method that I would like to be used (I don’t want to implement my own version).
Ah, thanks!
I’m working on an update to https://github.com/zeekat/ring-openapi-validator
I have a function with declared like this:
(defn bdd-to-dot
"Create (and possibly display) a graphical image rendering the given Bdd
For Mac OS, the :view option may be used to display the image
interactively."
[bdd & {:keys [title view verbose pen-width draw-false-leaf]
:as all-optionals
:or {title "no-title"
view false
verbose false
pen-width 2
draw-false-leaf true}}]
...
and within the function I want to call another function passing along the same all-optionals
, I tried with (apply second-function arg1 all-optionals)
and I get a java exception about a missing argument.
Is this supposed to work, or am I completely on the wrong track? Do I really need to redundantly re-type all the :key value pairs at the call site?when hashmap is used for applying it will be turned into sequence of pairs. try to call it like this (apply second-function arg1 (mapcat identity all-options))
and if you are using clojure 1.11-alpha you can avoid using apply at all. Just call second function with two arguments arg1
and all-options
I'll give it a try. if it works, it would be great to add to https://clojuredocs.org/clojure.core/apply, which I'll gladly do.
I say a syntax in the clojure doc about (apply fun arg arg arg & args
). What is that for?
internal optimisations
look at (source apply)
ahhh, I misunderstood the doc. I though I could use the & at the call-site. no, it's just part of the declaration. gotcha
I added an example to the `apply` docs. https://clojuredocs.org/clojure.core/apply#example-60f9450ce4b0b1e3652d7519
If I call the second function as follows, it works correctly but this seems unnecessary to me:
(second-function arg1
:title title
:view view
:verbose verbose
:pen-width pen-width
:draw-false-leaf draw-false-leaf)
Followup: this code in
seems to be what’s preventing the default method from being found:
;add methods matching interfaces', if no mapping -> throw
(doseq [^java.lang.reflect.Method meth ifaces-meths]
(gen-method meth
(fn [^GeneratorAdapter gen ^Method m]
(. gen (throwException ex-type (. m (getName))))))))
;finish class def
In other words, proxy
will fill in all declared methods in the interface, overriding any defaults.I added an example to the apply
docs. If anyone sees something I said incorrectly, let me know. I'm happy to change it to make it correct. https://clojuredocs.org/clojure.core/apply#example-60f9450ce4b0b1e3652d7519
is anyone aware of someone doing something like graalvm compiling rebel-readline + cider connect or something similar into a terminal repl connect type tool?
is it this? https://github.com/borkdude/reply
https://github.com/l0st3d ... many thanks
I'm using concat
to lazily generate a potentially exponentially explosive list. I think I'm only generating pairs as needed. Is there a function I can used on a lazy seq to ask "how many element have been produced, ignoring the ones that haven't been produced yet?"
but if you are generating the elements yourself you could just update a counter somewhere imperatively
I think he wanted this for debugging. So I think a plain (println _)
would also do.
You can do it but the collection will still remain in memory. There are macros that consume the collection and don't retain the head of it. You could have a counter where you consume your list.
I think it's possible to do it via realized?
to find the next item that's still not created but I don't think it's a good idea.
Edit: I was thinking about actual solution for prod code and not debugging.
The pattern I'm using is something like the following.
(letfn [(f [node lineage]
(cond condition-1
[(g node)]
condition-2
[]
:else
(concat (f (:left node) (cons node lineage))
(f (:right node) (cons (h node) lineage))))]
(f some-node ())
@invertisment_clojuria I only need this for debugging, to do a sanity check that my algorithm does what I think it does.
Why not println
then?
And yes, a separate counter would do.
Then I'd do an atom or an agent which you'd update from the f
.
Why don't you simply do (take 20 coll)
? Why do you need a count?
Also you could do lazy-seq
in your concat
so that the collection wouldn't blow up your RAM.
Edit: probably concat
aleady does this. I confused it with cons + lazy-seq
.
should I be using concatenation of lists or of vectors [] here? if I use vectors, won't the head of the vector be retained no matter how I iterate? whereas with lists, once I've popped it of, it can be GC'd right?
Well you return [(g node)]
from your method. Which means that your vector contains one item.
So you concatenate many of those (1-len vecs) and get one long seq of small things. So I don't think it's a problem for your memory.
yes, but then its concatenated on the right-hand-side with another vector of length one, right?
But concatenation is lazy, not immediate. You don't create a vector from those vectors. You create a lazy-seq via (concat [:a] [:a])
If you'd get vector back you'd blow up your ram at the moment you call f
.
ahhh. I'm not creating a vector!! that's even better than I thought.
If you want a vector you can do this (please don't): (into (f ...) (f ...))
. This will blow up your ram for sure.
@jimka.issy Also you use (cons node lineage)
which creates a seq
but it's not lazy. But this shouldn't impact your code as you don't do any calculation to get lineage
sequence. It would make a difference if you'd do this: (cons node (f ...))
, which wouldn't lazily calculate f
. Then you'd be better off if you'd have this: (cons node (lazy-seq (f ...)))
.
Here's the code after your suggestions. Thanks.
(defn satisfying-type-designators
"Create a lazy list of type designators which satisfy the Bdd. I.e.,
one element of the list for each type corresponding to the nodes
from the top of the Bdd to the true leaf."
[bdd]
(assert (clojure.core/or (instance? Boolean bdd)
(instance? Bdd bdd)))
(letfn [(satisfying [node lineage]
(if (= false node)
[] ;; if reached a false leaf-node
(let [td (gns/canonicalize-type lineage :dnf)]
(cond (= :empty-set td)
;; if the lineage canonicalizes to an empty intersection, then we prune this recursion
[]
(= true node)
;; if reached a true leaf-node
[td]
:else
;; otherwise, we generate a lazy conatenation of the left and right traversals
(concat (satisfying (:positive node)
(gns/create-and [td
(:label node)]))
(satisfying (:negative node)
(gns/create-and [td
(gns/create-not (:label node))])))))))]
(satisfying bdd :sigma)))
BTW I discovered I can prune the recursion in some cases also I don't really need to cons the lineage. I can just incrementally compute the accumulated value as the recursion occurs.
I'm yet again frustrated by maven and gradle on the java/scala side and don't see a compelling reason the config format can't be used to build those projects also
beyond the part of the file that is just <dependencies>
it is just hard to know what is happening where, why, and by whom
like, I understand kinda maven's lifecycles. everyone i've met seems to really like gradle because its "simpler" (read - easier)
but the only commonalities between them - and the only thing I really care about in that tool in particular - is 1. fetch deps, 2. compile all files in directory, 3. build jar, 4. run tests (5. work with IDE, but one thing at a time)
I would much rather if generating code for say, a WSDL interface or protobuf, had me calling a command line tool (that I knew would be available ...somehow) and just composing tools like that
So are you looking to switch tool chains for projects over to deps.edn entirely? Or to provide a translator front end that produces the appropriate config for maven/gradle/abt
While your adrenaline is still pumping, could you write up a example for one the tool chains? Just so that there is a document with the flavor of what you have in mind. I’d be happy to collaborate…
{:deps {some/lib {:mvn/version "123"}}
:aliases {:uberjar {:replace-deps {com.github.seancorfield/depstar {:mvn/version "2.1.267"}}
:exec-fn hf.depstar/uberjar
:exec-args {:aot true}
:other-tool {...}}
:test ; for a testing context
{:extra-deps {com.gfredericks/test.chuck {:mvn/version "0.2.10"}
expectations/clojure-test {:mvn/version "1.2.1"}
org.clojure/test.check {}}
:jvm-opts ["-Dclojure.core.async.go-checking=true"
"-Dclojure.tools.logging.factory=clojure.tools.logging.impl/log4j2-factory"
"-Dlogged-future=synchronous"
"-XX:-OmitStackTraceInFastThrow"
"--illegal-access=warn"]}
:runner ; to run tests (test:<subproject>-test:runner) -- the <task> called test
{:extra-deps {org.clojure/tools.namespace {}
org.clojure/tools.reader {}
com.cognitect/test-runner
{:git/url ""
:sha "b6b3193fcc42659d7e46ecd1884a228993441182"}}
:jvm-opts ["-Dlog4j2.configurationFile=log4j2-silent.properties"]
:main-opts ["-m" "cognitect.test-runner"
"-r" ".*[-\\.](expectations|test)(\\..*)?$"]}
but instead of your tools being "include this jar, call this function" you have "plugins"
plugins {
// Apply the scala plugin to add support for Scala
id "scala"
id "com.github.maiflai.scalatest" version "0.23"
}
repositories {
// Use jcenter for resolving your dependencies.
// You can declare any Maven/Ivy/file repository here.
jcenter()
mavenCentral()
}
def scala_minor_version = "2.12"
dependencies {
implementation project(":dao")
// Automatic resource management
implementation "io.tmos:arm4s_${scala_minor_version}:1.1.0"
// Elasticsearch client
implementation "com.sksamuel.elastic4s:elastic4s-http_${scala_minor_version}:6.5.7"
// Json serialization
implementation "com.typesafe.play:play-json_${scala_minor_version}:2.6.10"
// Slick. Needed for 'MappedTo' macro in DAO entities
implementation "com.typesafe.slick:slick_${scala_minor_version}:3.3.0"
// Google guava for caching
implementation "com.google.guava:guava:27.1-jre"
}
plugins {
// Apply the scala plugin to add support for Scala
id "scala"
id "org.scoverage" version "2.5.0"
id "com.github.maiflai.scalatest" version "0.23"
}
repositories {
// Use jcenter for resolving your dependencies.
// You can declare any Maven/Ivy/file repository here.
jcenter()
mavenCentral()
}
def scala_minor_version = "2.12"
dependencies {
implementation project(":dao")
// Automatic resource management
implementation "io.tmos:arm4s_${scala_minor_version}:1.1.0"
// Json serialization
implementation "com.typesafe.play:play-json_${scala_minor_version}:2.6.10"
// Stream editing
implementation "com.github.rwitzel.streamflyer:streamflyer-core:1.2.0"
// Slick DB dependencies
implementation "com.typesafe.slick:slick_${scala_minor_version}:3.3.0"
implementation "com.typesafe.slick:slick-hikaricp_${scala_minor_version}:3.3.0"
implementation "mysql:mysql-connector-java:8.0.15"
// Use Calcite parser
implementation 'org.apache.calcite:calcite-core:1.26.0'
implementation 'org.apache.calcite:calcite-server:1.26.0'
implementation "com.h2database:h2:1.4.199"
scoverage "org.scoverage:scalac-scoverage-plugin_${scala_minor_version}:1.4.0-M5",
"org.scoverage:scalac-scoverage-runtime_${scala_minor_version}:1.4.0-M5"
}
checkScoverage {
minimumRate = 0.75
coverageType = "Branch"
}
ThisBuild / scalaVersion := "2.13.6"
ThisBuild / organization := "com.example"
val scalaTest = "org.scalatest" %% "scalatest" % "3.2.7"
lazy val hello = (project in file("."))
.settings(
name := "Hello",
libraryDependencies += "com.eed3si9n" %% "gigahorse-okhttp" % "0.5.0",
libraryDependencies += scalaTest % Test,
)
lazy val helloCore = (project in file("core"))
.settings(
name := "Hello Core",
libraryDependencies += scalaTest % Test,
)
this is maybe a bad sbt example, but it is a lot of semantics to understand to make a hello world
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
<generatedSourcesDirectory>${project.build.directory}
/generated-sources/</generatedSourcesDirectory>
<annotationProcessors>
<annotationProcessor>
com.baeldung.annotation.processor.BuilderProcessor
</annotationProcessor>
</annotationProcessors>
</configuration>
</plugin>
</plugins>
</build>
this is from this tutorial https://www.baeldung.com/java-annotation-processing-builder
but where this immediately hurt my brain was making a multi module project - absolutely needed to develop an annotation processor since you need a class compiled already and available at compile time to run it
{:paths ["src" "target/classes"]
:deps/prep-lib {:alias :build
:fn compile
:ensure "target/classes"}}
{:deps
{some.dep {:mvn/version "123"}}
:aliases
{:test {:extra-paths ["test"]
:extra-deps { {:mvn/version "123"}}
:main-opts ["--dirs" "src/test/" --report-dir" "somewhere"]
:exec-class org.junit.TestRunner}}}
extremely clear link to the method that will run (static main method of class, or in the case of the clojure ecosystem the clojure fn) and the configuration given to it
I also don't think it is needed to write stuff in anything but clojure for a proof of concept or even as a final stage, but EDN is just a data format. There is no intrinsic reason only clj
can handle it
(its fascinating that when my job was java I would write clojure on the side, but now the situation is reversed)
I think you write java in a different way. Not so much mutable state, probably persistent data structures too. And maybe more static methods as well. It's not the same anymore.
At least these were the observations that I noticed about myself.
Also from time to time I do some golang. Which is completely something else.
I was stupid enough to force FP on my first java project when I was still too much of an idiot to do it well. I still feel sorry for those people living in that codebase
I don't think I use more static methods. Java still works best with having methods targeted - but I am more prone to using mixins than the average bear.
personal favorite pattern I can't wait to be the only one in the world using
public sealed interface ThingOps permits Thing {
default void doX() {
var self = switch (this) { case Thing t -> t };
System.out.println(self.x());
}
}
public final class Thing implements ThingOps {
public int x() {
return 5;
}
}
Ah, so that's why they bought github...
Someone on my team has access to it, they haven't been blown away by it for Clojure.
What about other languages? Which ones were they blown away by? What about JS?
One thing it does do is get context really well, it seems to often predict:
(let [foo 1
bar 2
baz 3] |)
into
(let [foo 1 bar 2 baz 3]
{:foo foo
:bar bar
:baz baz})
apparently. That's my understanding anyway.It's not been trained on a huge set, so I guess there's a lot of code like that about.
https://6b.eleuther.ai is similar (GPT-J instead of GPT-3), and runs in the browser, but is slooow. Maybe fun to play around with
Some information about it here https://www.infoq.com/news/2021/07/eleutherai-gpt-j/
That's where I think things like CoPilot will shine: utility functions. But we don't have many in Clojure.
(defn distinct-by [f s]
gave me
(defn distinct-by [f s]
(zipmap (map #(f %) (partition 2 s))
(reduce (fn [acc r]
(let [xs (map #(f %) (partition 2 r))]
(assoc acc xs)))
{}
s)))
Where assoc has the wrong number of arguments.I always wanted to have (partition-by f coll)
. Probably now it will happen easily :))
Actually what I'd prefer more is that it would allow to find snippets of code which I could delete or replace. Not to write in the first place. Because if it will do some repetitive code in a sub-par way in which other devs do it then its easier to produce a wreck faster if you don't understand what it does. i.e. I could generate getters and setters for all objects using intellij but in some languages they choose not to have them. So why would I want to generate factories/singletons/repetitive-fetching if I decide not to have those?
what is the point of a fancy AI coder if you need to check its code output manually?
To train the actual AI coder that does both things 😉 It was never meant to help you 😉
well let me not help them train any AIs then...
Yeah... I'm so tired of this is the same feeling when I read the terms and decide that I don't want to be a part of something. I wonder when we'll reverse to the world that actually tries to genuinely help you without hidden intents... Probably it never existed outside of my imagination...
cl-format is cool! I just discovered ~&
and ~@
. I added a note to https://clojuredocs.org/clojure.pprint/cl-format#example-60f98682e4b0b1e3652d751b
I have a function that creates a string from a map like so
(def export-options {:query {"_submission_time" {"$gte" "2021-04-20T00:00:00", "$lte" "2021-04-21T23:59:59"}}})
(clojure.string/join "&"
(for [[option val] export-options]
(str (name option) "=" val)))
this however returns a string with backslashes like so
query={\"_submission_time\" {\"$gte\" \"2021-04-20T00:00:00\", \"$lte\" \"2021-04-21T23:59:59\"}}
I would like it to look like this
query={"_submission_time" {"$gte" "2021-04-20T00:00:00", "$lte" "2021-04-21T23:59:59"}}
Any ideas on how I can do that? I realized it's because of the str function. Wondering if there are other alternatives
ah, then it's working - the output is escaped but if you were to print it the backslash won't be rendered
input is a map
{:query {"_submission_time" {"$gte" "2021-04-20T00:00:00", "$lte" "2021-04-21T23:59:59"}}}
Expect output is a string
"query={"_submission_time" {"$gte" "2021-04-20T00:00:00", "$lte" "2021-04-21T23:59:59"}}"
remember, to indicate a string data type, strings are inside of quotation marks "
. And one marks the end of the string. So how do you indicate that a quotation mark should be a member of that string?
@U11BV7MTK sorry i have updated my answer @U0JEFEZH6 if i am understanding you correctly the escaped characters are because of the quotations?
see how the delimiters (the "
at the beginning and end are the same as the quotation marks inside of the string?
right. so we need a way to distinguish the "
as a delimiter of a string when printing and writing it, and "
as a valid character of a string, not as the kinda symbols we use to delimit the string
there are others as well. n
is obviously just the letter n
, but \n
when escaped is a newline
What are the webframeworks used with clojure that are good/preferred by the community? I am currently trying to use pedestal... but there isn't much support on it. So I am struggling a bit.... What other options do I have... ?
Might be too harsh a change, but you can do things without any frameworks. :) Think of what kind of pieces you need to achieve some goal, and search for libraries that fit the descriptions of those pieces. E.g. I generally prefer using yada. With hugsql if a DB is needed. With sente if websockets are needed. With buddy if auth is needed. And so on.
A bit on the above, plus some descriptions of some frameworks: https://purelyfunctional.tv/mini-guide/what-web-framework-should-i-use-in-clojure/
Frameworks in general are not considered very Clojure-y -- folks generally use a combination of their preferred libraries. That said, you might find Luminus (a template that gathers together many web-related libraries) helpful, or you might look at Coast.
Ah, yes. I tend to forget Fulcro because I think of it as very frontend-heavy and I don't do cljs/js/frontend stuff!
Ring + Compojure is the most common approach when getting started I think.
yes I do like clj and deps.edn. I use clj-new already...
BTW @U04V70XH6 is there a tutorial on how to tweak clj-new to create a custom template? - maybe this should be a separate thread... I'll put this in clj-new thread as well...
FWIW pedestal is a fine and actively supported framework, I keep knowing of companies launching their new projects in it its only problem is that it's 'boring' (stable) which for a good chunk of us is perfect!
there are online docs, there's also a template and #pedestal . More than enough to get started and often get questions answered by experts/authors Perhaps for a super specific problem you won't find the answer in the online docs, but honestly there comes a point where that's just part of software engineering. Reading the source and all that it's not all paint-by-numbers :)
IDK, some frameworks/libs/... prioritize getting users above everything else and others don't. It seems to me that Pedestal falls into the latter category. There are so many things that can be improved other than user count, e.g. performance, design, or even a nice work-life separation for their authors. I do perceive some user acquisition obsession in some JS projects.
sure, neither of us needs to extrapolate what the other supposedly suggested. Sorry if I brought the thread to bit of a tangent.
I think that, for a lot of Clojure developers who've been working with the tech and the ecosystem for several years, it's very hard for them to put themselves in the shoes/mindset of a beginner and even be able to judge whether a project and its docs are approachable for beginners. I know I have a very hard time recommending good beginner-friendly material a lot of the time. For example, I thought depstar
was pretty straightforward to use but as more and more new developers are coming to the Clojure CLI and deps.edn
stuff, I found I was getting a lot of questions about stuff that I didn't realize beginners didn't know -- so I ended up adding this page to the docs https://cljdoc.org/d/com.github.seancorfield/depstar/2.1.267/doc/terminology-convention which seems to have helped with that (somewhat). Sometimes I even have a hard time understanding what a beginner is asking because I inherently make assumptions about what they already know... 😞
I'm always happy to try to field Qs about the deps.edn
/tools stuff -- via DM if folks prefer, where I can got into really long-form discussions.
I thought there was a glossary on http://clojure.org but I can't find it now. There's an FAQ (under Guides) but it isn't very extensive yet. There are several suggestions for additions to the FAQ (and improvements to the site in general) here: https://github.com/clojure/clojure-site/issues
The closest thing is https://clojure.org/reference/deps_and_cli#_glossary I guess...
No, I that's very reasonable. And thank you for linking to my guide. I'd love to be able to remove mine and just point to the equivalent stuff on the official site 🙂

@U45T93RA6 There is not very good documentation on pedestal unfortunately. You can look at examples but unless the example does exactly what you want to do... it is hard to discern things... e.g. 2 questions I posted on the pedestal clojurians thread.. 1. Why is my response not rendering into HTML Ans: You need to explictely set the header content type to html... I challenge you to find this in any pedestal documentation. 2. How can I map multiple folders from my development environment to the hosted sources... Ans: Some very bizzare combination of :paths and a new :html-filepath setting in the service record... again challenge you to find this info in documentation. I grant you I am a total newbie and complete numpty on most days.... so maybe these things are obvious to everybody else... but so far my journey with pedestal has not been good... I tried luminus as well... it was too much for me. I'll try yada... as I looked at it yesterday and the documentation looked quite exhaustive... lets see.
1 - Honestly that's basic web dev knowledge. You can experience that problem under any framework; a web developer will know to inspect a web response (under your browser's devtools or curl
output) and make sense out of it.
It's not any framework's responsibility to teach you webdev. Same for classpath, while we're there. There's googling, and also there's schools, or friends, mentors etc.
Else we could take the whole notion of explaning everything ad nauseam. Where's the limit? Should http://clojure.org teach me about physics?
2 - you got a precise answer for your question. Personally I'd simply feel happy for that and wonder if I'm to qualified to describe it as bizarre while I also describe myself as a total newbie
.
Food for thought perhaps, if not feel free to disregard my ramblings!
look I am not here to pick a fight. I am merely telling you my experience. If someone new picks up pedestal... and follows their own examples from their own pages... and ends up with mere text instead of html... well that is not a good start. Also I got a precise answer to my point 2 because I was on here. I struggled by myself for almost 6 months before getting on here. Clojure is a hard enough ecosystem to learn without having to chase ones own tail trying to find answers.... and I know people do lots of complex things with a webframework... but the most simplest thing is hosting static resource... everybody probably starts from that... I think/hope... anyways... I am merely voicing my experience. I totally accept that I am not a seasoned web dev.
@U45T93RA6 I'm going to wade in here and say that is the sort of gatekeeping that really puts new developers off Clojure: "basic web dev knowledge" -- a new developer isn't going to understand a lot of the concepts and isn't going to be able to make sense of what they find on Google/Bing a lot of the time because it's hard to discern the audience for a lot of the articles out there. On the flip side @U022LK3L4JJ this is exactly why I recommend new developers ask questions in #beginners instead of #clojure because in #beginners people have opted in to help new developers and don't come back with this sort of "just Google it" answer...
(and, yes, there is an assumption that in the #clojure channel, developers do have some "basic ... knowledge" and know enough to be able to navigate search results etc -- I just want to point out how/why that is not helpful for beginners)
As food for thought, an acknowledged piece like https://gist.github.com/richhickey/1563cddea1002958f96e7ba9519972d9 can also been seen as "gatekeeping" More often than not I help all sorts of people, that's visible work for people who hang out in #clojure or github and don't appreciate the whole rethoric of shaming and name-calling when one doesn't follow a very specific implementation of how one 'ought' to welcome beginners We live in splendid times for being a beginner, I'd simply be wary of taking this welcoming culture to implementational extremes
Let's leave it at that, I'm simply sharing my views on a topic that I perceive from time to time regardless of the individuals involved Personally I just enjoy questioning the nature of things, sometimes nice exchanges arise from it
At the risk of going further into #off-topic land here, Rich's piece is not about "gatekeeping" at all -- nothing about what he says creates a barrier to entry for new developers coming to Clojure. What he (rightly) complains about in that piece is the entitlement that some folks show in regards to how they think he should run his own project and the "demands" they make of OSS maintainers (and as a maintainer of OSS for close to thirty years now, I have to say I agree with him).
But, yeah, we should probably draw a line under this thread -- and take it elsewhere if we wish to continue.
This is a library that I recommend to beginners so they can understand how routes map to code but I think @U022LK3L4JJ is looking for something more akin to web frameworks in other languages?
Frameworks in general are not considered very Clojure-y -- folks generally use a combination of their preferred libraries. That said, you might find Luminus (a template that gathers together many web-related libraries) helpful, or you might look at Coast.
^ As a perhaps interesting topic, it would help so much to have some sort of agnostic test suite for all things security when it comes to web frameworks/templates/compositions. It's often said that that security is lost when you assemble things by hand, that in that sense frameworks can be greater than the sum of its parts. Which of course doesn't need to be the case but in practice, quite plausibly is