![]() |
|
![]() |
| The thing is, in Go you can develop bottom-up, but you can't always end up with a an appropriate abstraction, because of the lack of tools to achieve that. |
![]() |
| Java 7 is already pretty pleasant, and it has been improving ever since then.
(Say what you want about Oracle Corporation, but as the wardens of Java they're doing a pretty good job.) |
![]() |
| Check out Babashka. It's a single binary that runs Clojure without a JVM. It also starts up really fast and has a much lower base memory requirement: https://github.com/babashka/babashka
It's pretty easy to switch to writing JVM Clojure if you're familiar with Babashka. Most of the libraries written for Babashka are designed to work in either environment. That said, there are reasons you may want to use the Clojure on the JVM later on. It might be interesting to read the replies to another poster with similar concerns about the JVM: https://news.ycombinator.com/item?id=40445415 |
![]() |
| If you are new to Clojure and would like to experiment with it in a way that is immediately useful, I highly recommend the Babashka runtime for scripting [0]. It's very fun, approachable, and one of the more polished parts of the Clojure ecosystem.
It's a particularly good entry point because unlike full-JVM Clojure it has a very fast startup time. Newcomers can use any file-watching /reloading tools (e.g. nodemon) that they're already familiar with to work with it interactively. Hopefully, a enthusiastic user will graduate to using a REPL connection in their editor for a fully interactive setup. But newcomers tend not to do this... its an unfamiliar workflow to most, and can be pretty cumbersome to setup. [0]: https://babashka.org |
![]() |
| The best part about Babashka is that it's really batteries included nowadays. I had to make a little UI to display some stats about an app at work, and decided to try using it with HTMX. Turned out to be a really good experience. Babashka has pretty much everything you need for a basic web app baked in, and HTMX lets you do dynamic loading on the page without having to bother with a Js frontend.
Best part is that bb can start nREPL with `bb --nrepl-server` and then you can connect an editor like Calva to it and develop the script interactively. Definitely recommend checking it out if you need to make a simple web UI. Here's an example of a full fledged web app:
|
![]() |
| I agree. It's a breath of fresh air in the Clojure world. I'm grateful to thoughtful builders like yourself and borkdude for bringing the language to new heights. |
![]() |
| Babashka is definitely the most exciting thing currently happening in Clojure world in my opinion. And thanks, always great to hear my stuff ends up being useful. :) |
![]() |
| Reagent is nice and has been around for about a decade now, but I moved away from it towards very thin wrappers around React[1], because I felt like it was adding too much additional complexity on top of React, which is already quite complex on its own. I wanted a clearer view at what is going on and a simpler way to interop with native React components.
Although it seems to catch up with experimental support of React 18 now, Reagent has fallen behind the latest developments in React and may not benefit from all of its performance optimizations. It is still using class components instead of hooks and there have been concerns that the runtime conversion of Hiccup may drag down performance. I guess in most cases it is not really an issue or in any way noticable, so if you’re not doing any fancy stuff it should be fine. I may even come back to Reagent at some point, since I have to admit that I miss the UI-as-data model with Hiccup. What I highly recommend, however, is using re-frame[2] for state management. It has also been around for a long time (2014, around the same time Reagent came along) and pioneered some popular ideas in that area. It may seem a bit overwhelming at first, but the docs provide a great introduction and I find the model very clear once you wrap your head around it. At the moment it depends on Reagent, but there are ways around that. [3] [1]: see Helix (https://github.com/lilactown/helix) or UIx (https://github.com/pitch-io/uix) [2]: https://day8.github.io/re-frame/ [3]: refx (https://github.com/ferdinand-beyer/refx) is an almost drop-in replacement without the Reagent dependency, but hasn’t been updated in a while. Alternatively, re-frame can be integrated with UIx/Helix by adding some interop code https://github.com/pitch-io/uix/blob/master/docs/interop-wit... |
![]() |
| Another website in this vein is https://www.maria.cloud/ which is more geared up for teaching complete beginners programming, but I think with the basic paredit style controls and the evaluate-each-form-at-a-time style is I think a lot more realistic to the kind of REPL driven development that you actually use — typing directly into the REPL terminal window is very unergonomic imo. You’re much more likely to use something like VSCode’s Calva or emacs’ cider to send the form under the cursor to a REPL process somewhere.
|
![]() |
| I have to admit, several years ago, a colleague of mine advised me if not to try Clojure but at least to read the "History of Clojure": https://dl.acm.org/doi/pdf/10.1145/3386321, which I never did. But one day I decided to watch Rich Hickey - Greatest Hits https://changelog.com/posts/rich-hickeys-greatest-hits... I then read the "History of Clojure", and then jumped into learning it.
This is probably one of the most fun languages to build with and one of the most beautiful ones. If not syntax-wise, rather in a way it allows you to express your thoughts via good design and composition that so nicely tickles your brain. If you are still searching for that one shiny tool, and none of them clicks, maybe try Clojure. It's one of the most concise and yet powerful languages I've seen.
|
![]() |
| I got what you described by learning Common Lisp just a few months ago. Do you think learning Clojure would get me something in addition to that or is it roughly the same? |
![]() |
| There's an ongoing effort to create a Clang/LLVM implementation of Clojure's runtime with hot reloading and other very interesting features. You can take a look at it at https://jank-lang.org/. It still hasn't reached feature parity with full blown JVM Clojure but we've paying close attention to its development.
|
![]() |
| Babashka scripts are interpreted, the babashka binary itself is compiled with GraalVM.
But: you can also build regular Clojure programs using GraalVM and create a fast-starting binary. |
![]() |
| Nubank purchased Cognitect (the consulting company behind Clojure) in 2020, as well as Platformatec (employers of Jose Valim); the latter was for access to Platformatec's project management expertise (see https://building.nubank.com.br/tech-perspectives-behind-nuba... there is no Elixir code running at Nubank AFAIK.
There is Python on the "other side" of the ETL pipeline, but everything user-facing is (again AFAIK) Clojure on the backend and TypeScript in the Android and iOS apps. |
![]() |
| Oh, interesting. Yeah, that's even better for an onboarding tutorial. I didn't know it existed (used Clojure for 6 years a long time ago).
Cool: https://shaunlebron.github.io/parinfer/ I always thought that the surrounding tooling (having to learn how to edit parens productively, using nrepl/cider, maybe even emacs... with evil-mode of course) to be both the worst and then eventually the best parts of Clojure. |
![]() |
| I had already used vim by the time I found clojure during uni (back when I had the energy to learn major new things), but vim support sucked for things like evaluating code blocks in the buffer, so I tried emacs and immediately slapped on evil-mode (vim bindings inside emacs).
For those six years I don't think I used emacs keybindings during that time except to move between files and execute clojure code. I couldn't be arsed to learn it. It was basically a fancier vim, haha. These days I use VSCode for all software. At some point in my 20s I found out there's life outside of coding so now I use a less esoteric editor. I'm sure its clojure / nrepl / paredit / parinfer support is fine. (Seems to be this: https://calva.io/) Back in 2010 the options weren't as great. |
![]() |
| I'm the same, always just used parinfer with IntelliJ, though I do know a few paredit key combos that I use occasionally (paredit isn't disabled just because parinfer is enabled). |
![]() |
| TryClojure author here. Thanks for the suggestions! I’ll look into adding parinfer soon and maybe add a section on the benefits of integrating Clojure with the editor. |
![]() |
| In Perl you have the dynamism, and the syntax.
Evidently, 93% paint splatters, when OCRed, are valid Perl programs: https://www.mcmillen.dev/sigbovik/2019.pdf I doubt that a similar thing is true for Clojure. Here, the only problem is that you don't know what the symbols and some of the notations mean. You can look at the unfamiliar syntax and know what is a child of what. Most operators are words you can search for. |
![]() |
| Clojure looks productive. What frameworks libraries do people use to build web apps? Like, replacement for Django view layer and ORM (not looking to debate orms thanks)? |
![]() |
| Biff looks neat, thanks! I mainly just don't want to write raw sql/mappers to structures for simple queries. Looks like most sql libraries with clojure can just return maps so that's neat. |
![]() |
| In Clojure you'll have to write the queries yourself unfortunately. People always ask, where is the fully fledged web framework in Clojure? There isn't one. Why there isn't one is hard to answer, but it's partially because the people who could write one, don't find they need one themselves.
There's definitely a preference in Clojure for not relying on frameworks, because the current people in the community like to be in control, know what's going on, or do it their own way. That said, the whole code still ends up being relatively small. So, you kind of end up with a similar amount of total code, but you're much more in control. And if certain things you find too repetitive, you can remove the repetition yourself through many of Clojure's facilities, specifically where they annoyed you. See: https://github.com/didibus/simple-website-with-posts where I implemented the small website you were talking about, creating posts and seeing them. The whole code is here (minus the CSS): https://github.com/didibus/simple-website-with-posts/blob/ma... It's 95 loc and that includes the templates. There's no framework. |
![]() |
| The key thing is experienced Clojure programmers often see a lack of ORM as a feature rather than an oversight. There were some more ORM-like libraries years ago (see Korma) but my impression is that people ultimately didn't want this and moved on to lower-level JDBC wrappers combined with HoneySQL. I found a more detailed discussion on Reddit about Clojure and ORMs back in 2020 if you want to get more info: https://reddit.com/r/Clojure/comments/g7qyoy/why_does_orm_ha...
Note that I'm not making a value judgement about Python/Django or any other library/framework combination. It's obviously a valid path, but Clojure is a different path. I can assure you there are straightforward solutions to create readable APIs like the Django example with minimal boilerplate, but the approach is fundamentally different from Python/Django. If you do decide to build something in Clojure and think, "I already know how to do this in Django, why is it missing?", don't hesitate to join the Clojurians Slack and hop into the #beginners channel. There are plenty of people who can help you there. |
![]() |
| It's kind of surprising the most popular(?) web framework uses a weird/obscure db
I have no doubt that XTDB is good, it is just surprising as a first choice and is not going to be easy to get up and running compared to the conventional alternatives (Is there even a hosted offering for XTDB? I like that it's open source but seems that you will have to grapple with all this stuff including a Kafka cluster https://docs.xtdb.com/guides/starting-with-aws.html) |
![]() |
| Along the same lines with the docs, I also find it frustrating that a lot of the very most core basic abstractions and interfaces are left totally undefined in terms of documentation. Take `ISeq`'s definition. Surely, a candidate for the single most core interface.
https://github.com/clojure/clojure/blob/master/src/jvm/cloju... But like, where is the javadoc? What exactly is supposed to be the contract of these methods `first`, `next`, `more`, `cons`? What's the difference between `next` and `more`? I really just don't like that. Are we just supposed to pick up the core contracts/abstractions through oral teachings and slack channel messages? |
![]() |
| i wish it was easy to make android apps with clojure. not sure if itd be easier with jetpack compose + clojure or react native + clojurescript. |
![]() |
| Clojure 1.12 (which is nearly done) is going to add a bunch of interop support - method values, array class syntax, Clojure fn -> Java functional interface conversion, stream support, etc. |
![]() |
| I also cannot stand Java (not the technology, but everything surrounding it - bean factories, J2EE, Tomcat, war files, maven, insane unhinged class paths etc) but honestly one of the reasons Clojure is amazing is that it allows you to exist inside of the Java ecosystem while also being pretty isolated and walled off from it. It's all the good parts of Java without all the bad annoying parts.
I would urge you to push thru the PTSD and give it another shot. There is also Clojurescript, which runs on Javascript, and Babashka which is a lighter weight implementation of Clojure for fast startup times that is targeted at things like shell scripts or system programs. https://babashka.org/ |
![]() |
| It's everything to do with the libraries people pick and not Java the language. Java the language can be extremely concise and expressive. Stop picking Spring and Java becomes fun again. |
![]() |
| In a way it is, since Clojure typically both compiles but doesn’t really hide its connection to either Java or JS, so you can’t ignore the underlying target language. |
![]() |
| I was hoping it would be longer, like the tour of golang.
As is it basically just shows you how to do arithmetic, which isn't interesting at all. |
![]() |
| > unlike full-JVM Clojure it has a very fast startup time
I can't believe that after all these years Java still didn't fix their startup time. |
![]() |
| The JVM cold-starts, loads a Hello, World program from a compressed JAR, runs it and shuts down in 40ms. But Clojure compiles quite a bit of Clojure code generating hundreds if not thousands of classes and then loads them before starting up the Clojure program. Still, there's ongoing work on the JVM (Project Leyden [1]) to speed up both startup and warmup even of such programs by caching more state.
[1]: E.g. see https://spring.io/blog/2023/10/16/runtime-efficiency-with-sp... |
![]() |
| Why can't you do some JVM equivalent of memcopy/execve the starting state of the program?
Isn't the initialization procedure (or at least the vast majority of it) exactly the same at each run ? |
![]() |
| Right, but Native Image comes with its own tradeoffs. Lyeden's "premain" work aims to be somewhere between the situation today and Native Image. |
![]() |
| Right, but the problem is that many programs do a lot more work when they start up than Hello, World. The Clojure runtime in particular does quite a lot at startup. |
![]() |
| The various JVM implementations have mechanisms to fix that, like JIT caches and AOT compilation, which Clojure doesn't take advantage of.
So it is indeeed a Clojure issue, not a JVM one. |
![]() |
| AOT compilation with PGO, available for free on GraalVM and OpenJ9.
Also available since around 2000 from comercial vendors, of which, Aicas and PTC are the main survivors. https://www.aicas.com/wp/products-services/jamaicavm/ https://www.ptc.com/en/products/developer-tools/perc OpenJ9 also does JIT caching across executions, https://eclipse.dev/openj9/docs/aot/ OpenJDK also does caching but at higher level, https://docs.oracle.com/en/java/javase/22/vm/class-data-shar... https://wiki.openjdk.org/display/HotSpot/Application+Class+D... Project Leyden plans to add a similar JIT cache like on OpenJ9, https://openjdk.org/projects/leyden/notes/02-shift-and-const... Azul and OpenJ9 have cloud JIT servers, that share execution heuristics and dedicate servers for highly optimizing compilers, https://www.azul.com/products/prime/cloud-native-compiler-fa... https://eclipse.dev/openj9/docs/jitserver/ Finally, although technically not really Java nor JVM, the Android Runtime (ART), does a mix of high performance interpreter written in Assembly, JIT, AOT compilation, and PGO sharing across devices via Play Store (cloud profiles). |
![]() |
| I think, in a lot of cases when people still complain about "the slow startup time of the JVM", they're really just talking about how the big JVM GUI apps (like IDEs) struggle to get started on heavily-loaded systems. And this, I think, is mostly just due to these apps eagerly reloading the most recent workspace on startup, and so pre-allocating big chunks of memory on startup to be ready for that — which can ripple out, on systems with low free memory, as other, colder processes all bottlenecking together on the IO of having their own memory written out to swap; and/or to having dirty mmaped pages forcibly-flushed to disk en masse so that the page-cache entries they live in can be purged.
Much more rarely — mostly when talking about writing CLI tools in a JVM language — people actually are complaining about the single second-or-so it takes the JVM to start up. (Usually because they want to run this tool in a loop under xargs(1) or find(1) or something.) This last second of startup lag is (AFAIK) quite hard to improve, as it's mostly not the JVM itself starting up, but the static methods of JVM classes being called as those classes are loaded — which can do arbitrarily much stuff before any of your own code gets control. (Due to legacy code expecting to read certain per-boot-dynamic info as static fields rather than as the results of static method calls, I believe the JRE runtime is actually required to do quite a lot of that kind of static initialization, to pre-populate all those static fields, just in case something wants to read them.) --- You'd think that GraalVM could inherently skip most of this, because the Graal compiler does dead-code analysis. "If nothing in your code reads one of those static fields, then nothing needs to write that field, so let's not invoke the static initializer for that field." But that's not true: static initializers are exported and called by the runtime — so they're always "alive" from the compiler's perspective. The Graal compiler isn't doing full-bore data-flow analysis to determine which static members of which classes are ever read from. I believe GraalVM does try to work around static initializers as much as it can, by pre-evaluating and snapshotting as much of JVM runtime's static initializer code as possible by default, converting it all into const data structures embedded in the class files before the native codegen step gets run on it (see: https://www.graalvm.org/latest/reference-manual/native-image...). It's not possible to apply this pre-baking to 100% of classes, sadly — some of these data structures need environment data like env-vars or system network config threaded into them on boot. (I wonder anyone on the Graal project is working on a fully-general static-initializer optimization pass, that does something like concolic execution to bake these initializers out into either fullly-constant data if possible, or if not, then constant "data templates" plus trivial initializer functions that just gather up the few at-boot context fragments, shove them into the "data template" using a low-level hook, and then memcpy the result out onto the heap and call it an object.) |
I use Clojure nearly daily at my job and at home. Sometimes it's standard Clojure, sometimes it's the excellent Babashka flavor which I use as a make-like task runner and Zsh-like script replacement. It's not the only language I use of course, and Go is a strong second place most of the time especially if I need something compiled to a single binary. But Clojure is where I generally feel most at home thanks to the irreplaceable REPL based development flow which is more like a dialogue with my program than your typical write compile run loop.
Combine that with it running on the JVM and you have a wonderful set of tools to get things done in a pleasant way.
I strongly encourage anyone with even a passing interest in Lisp and functional programming to give it a try. If you're using VSCode there is the excellent Calva plugin to help you out.