I'm glad this is back. A version of this existed in the ancient past and helped encourage me to try Clojure which ended up being by far the most impactful decision in my professional life. It went away for a while for reasons I'm unclear on.
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.
elwell 262 days ago [-]
> It went away for a while for reasons I'm unclear on.
As a side note, I'm always amazed by people who can use a highly expressive language (Clojure, Rust, even TS) but switch to Go when they feel like it (especially pre-1.18).
To me, switching to a less expressive language is painful and infuriating. I remember having to switch from Python to Java 5, and how everything started to take 3 to 5 times longer code to express.
Maybe the key thing is to only write small things in less expressive languages, like shell scripts, or small C functions to help performance or FFI, or, well, tiny Go utilities.
kubb 263 days ago [-]
Development in Go will always be more painful for some people than in Clojure or TS, because the language is intentionally hostile to abstraction, so you're thinking in higher level concepts, but you implement them in more steps than necessary. The program can never reflect the shape of the problem, because it will be riddled with "glue code" that implements the obvious dull steps that you abstract away when reasoning.
There's a population of people that don't like this kind of abstraction, because to understand the code you first need to understand the problem, the data flow and how it's expressed. They prefer to look at the issue "bottom up" than "top down", and for them Go is perfect, because instead of reasoning about what the module does, how the data flows, what the algorithm does, they can focus on the small: this is a loop that iterates through a slice, this is an if condition that depends on this variable.
There's no better or worse here, just two types of people. Some like to look at the whole bridge, some like to examine the individual nuts and bolts and joints.
ReleaseCandidat 263 days ago [-]
> There's no better or worse here, just two types of people.
No, two types of solving a problem. I like writing both (Haskell and Go) - for _myself_ - but the tooling and standard library and ... of Go is orders of magnitudes better.
kubb 263 days ago [-]
I would refrain from comparing Haskell - a research language intended for academia and created in universities, to Go - a pragmatic industry language intended for production and created in a megacorp. Why would Haskell's tooling be better? It's not reasonable to expect that.
ReleaseCandidat 263 days ago [-]
I'm actually surprised Go's tooling as as good as it is _although_ it is made by Google. But to be fair, GoogleTest is OK too.
nine_k 263 days ago [-]
Google has produced a number of quite good tools. See also Bazel, for instance.
During my time there, I was genuinely impressed by the quality of many of the internal tools. E.g. it took GitHub code review tools a long time to get on par with Google's internal code review tools from 2015.
ReleaseCandidat 262 days ago [-]
> Google has produced a number of quite good tools.
"Quite good" is exactly the wording that I would have used too. But Go's tooling is _really_ good; the language itself is, well, "quite good". And is still supported and actively developed!
kaba0 262 days ago [-]
Well, you are comparing the tooling of haskell and go. Of course in this comparison the difference will be stark, but there are plenty of languages that are sanely expressive, unlike go, and has decent tooling around it, like java, c#, perhaps scala if you want even more expressivity.
ReleaseCandidat 262 days ago [-]
> and has decent tooling around it, like java, c#, perhaps scala
All of these are perfectly mediocre and usable languages (in which I would write in if somebody pays me), but surprisingly even for myself I ended up really liking Go.
kazinator 263 days ago [-]
> they can focus on the small: this is a loop that iterates through a slice, this is an if condition that depends on this variable.
Then a week later, the squinty mole discovers the code is dead; not called at all. There is no slice and no variable.
vaylian 263 days ago [-]
> The program can never reflect the shape of the problem, because it will be riddled with "glue code" that implements the obvious dull steps that you abstract away when reasoning.
How does this affect readability when one tries to dive into an existing Go project? Is Go code harder to navigate than code in more expressive languages?
ReleaseCandidat 262 days ago [-]
Go really is the most "readable" code for anybody who has ever read something C (or Algol ;) like - so just about anybody. The only "strange" stuff is its `iota`, the method syntax and of course channels and goroutines. But that's nowhere comparable to some C++ or Haskell, "type heavy" Typescript or some Lisp macro DSL.
kubb 262 days ago [-]
This would depend on the size of the project, and the difficulty of the solved problem.
With a simple problem, and a small codebase, Go is easier, because you need to know less language constructs.
With a complex problem, and a large codebase, a more expressive language is easier if used properly, because it can highlight what's important in the solution. The ratio of signal to noise when reading code is higher.
nXqd 262 days ago [-]
We shouldn't overestimtae the complexity of expressive language like Clojrue vs Go. Go is perfectly simple and easy to follow, even compared with Clojure. If you come from background of Computer Science or programming first, Go is easier to follow.
kaba0 262 days ago [-]
I always feel this comparison is a fallacy. Assembly is also perfectly simple and easy to follow, like each instruction is trivial. Yet you will fail to grasp the whole, as “you are zoomed in too close”. I feel go has a good scale for many kind of tasks, but at the same time, it lacks the expressivity to change the zoom level, which I feel is needlessly limiting and makes the language a bad language for problems that require a slightly higher level of abstraction.
alabhyajindal 262 days ago [-]
I tried Go for a while after coming from JavaScript and didn't feel productive enough. I think this explains why exactly.
chuckadams 263 days ago [-]
While I share your perspective on go, I do prefer developing in a bottom-up way myself: I just do so with the intent of iterating my way to a higher level abstraction. Sometimes I see the abstraction right away and start with it, but I don’t want a language that gets in my way in either direction.
kubb 263 days ago [-]
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.
_1_1_1_1_1_1_ 262 days ago [-]
[dead]
nine_k 263 days ago [-]
A Lisp (like Clojure), or Haskell are great for playing around with things and developing bottom-up. I did just that for a day job sometimes. Of course, I then had to translate that to Python or TS, but once an idea is understood and tried, it's not such a big deal.
ReleaseCandidat 262 days ago [-]
That's interesting, for me Lisps, Python or TS (or Go) are great for playing around with things and developing bottom-up. And then I translate that to Haskell (or Rust or C++ or Go).
nXqd 262 days ago [-]
I do switch between Clojure and Rust depends on the problems I solve at hands ( prototypes vs building production ). And yes I need go through my checklist of each to switch from connecting data flows at high level to examine nuts and bolts at low level.
BaculumMeumEst 262 days ago [-]
Writing Go is less fun but the language has far better tooling, dead simple deployment, lower memory usage, generally higher performance, and is easier to onboard new developers. Also I find it way easier to read old projects after time away.
You can get incredible mileage out of Go using just the stdlib, built in tools, and the pure go port of SQLite. With Clojure it feels like I have to stitch a million random libraries together to get anywhere. Writing Clojure is delightful but the ecosystem and tooling pale in comparison, and at the end of the day I think that’s what matters.
If Clojure had great built-in tooling, better error messages (and maybe some SBCL-esque type checking features), and if Datomic was open source and less rough around the edges, I think the language would have been far more successful.
jakebasile 263 days ago [-]
I won't tell you there aren't stumbling blocks and impedence mismatches but most of them are relatively minor syntactical things. The faux-AST in my head gets typed out in the wrong format and I have to backspace and correct it (think function declaration differences). The biggest workflow difference is undoubtedly the lack of a REPL in Go, but that is somewhat made up for by Go's lightning fast compilation and testing tooling (probably its second strongest attribute after the excellent standard library). After a day or two of writing in one or the other my neurons seem to get used to it only to cause the same problem in reverse when I switch back. Overall, it's not too bad and a case of just accepting the tools available for what they are. I don't try to force Go to be Clojure or Clojure to be Go.
fire_lake 262 days ago [-]
Python is the new Java 5 here. The lack of multiline lambdas is incredibly awkward, leading to code that bounces you around the file.
gpderetta 262 days ago [-]
Lack of multiline lambdas is infuriating, but nested named functions partially make up for it.
a_wild_dandan 263 days ago [-]
I must use PyTorch for ML, but badly wish I could use TypeScript instead. Its type system demolishes anything else I've used. Writing anything from SPI communications, to server logic, to rich GUIs is a breeze. Then I work in Python and have to fight `Any`s, ambiguous arguments, more verbose code, and bizarrely slow run times. It makes me sad.
nine_k 263 days ago [-]
While I agree with you, Python is getting better in this regard, as is mypy.
Making historical interfaces well-typed is another kettle of fish. For instance, Python's range() is actually two overrides with incompatible signatures. I bet PyTorch has more of such examples.
pjmlp 262 days ago [-]
If it only had a V8 class JIT in the package as well.
harrison_clarke 263 days ago [-]
i find i flip between different languages when i'm thinking about the problem in a different way
an expressive language (clojure, python, C#, etc.) is good when you're iterating "what problem am i even trying to solve?"
a less expressive language is typically better when you more or less know a high level solution to your business problem, and you're iterating on "how do i make the machine actually do it". like "how do i reduce my AWS bill" or "how do i render it in 16ms?"
for something like gamedev, enough of the problem is in the second category that you might not even reach for the expressive language. (many do, though. with lua, or artisanal lisp dialects)
voidUpdate 262 days ago [-]
If you're counting C# as an expressive language then a lot of gamedevs will reach for one, judging by the market share of unity, me included :P
cess11 263 days ago [-]
Java has had lambdas, map/filter/reduce and other stuff for like ten years or so I think. It's a bit chatty for sure, but that's what you want sometimes, the verbosity can make it easier for newcomers to make changes and additions.
To me it isn't more infuriating than learning a new language or practicing one I'm not very good at. There's a bit of friction, but that's common when I'm developing in the languages I'm most fluent in too. Either that or the problem is trivial and likely to be in most common programming languages.
fiddlerwoaroof 263 days ago [-]
I’ve been writing a lot of Java 11 recently, and I find it to just be a very pleasant environment to work in: the IDEs still are best in class, afaict; the newer APIs avoid a lot of the boilerplate I used to have to write out and there are generally mature libraries to interact with just about any system you might need. Where Clojure really shines is that it’s a really well thought-out layer that can leverage this ecosystem and adds its own well-designed tools on top for my preferred more-interactive development style.
ErikCorry 262 days ago [-]
These Java features are ruined by not allowing mutable variables to be captured. Having to do the 1-element array trick when the compiler could just do it for me is insane.
If you are making JavaScript look good you are failing as a language.
cess11 262 days ago [-]
Typically I can use a forEach in such cases, and when I can't it's probably an incrementer and the IDE just switches out my int as an automatic solution to its complaint.
I think its OK to be reminded that the context is mainly immutable when chaining on a stream.
Method polymorphism to achieve default parameter values is a much worse nuisance, but not particularly hard to get used to when one decides to focus on the problem rather than the inevitable warts of a broadly useful programming language.
Edit: Also a pretty weird complaint in a thread about a programming language where pretty much all the basic data structures are immutable. Maybe take it to the top and make the quip about worse than JavaScript with regards to Clojure?
ErikCorry 262 days ago [-]
Doesn't the forEach method on arrayList have the exact same issue?
kaba0 262 days ago [-]
You mean not having the error that go just fixed by changing the language is a problem?
ErikCorry 262 days ago [-]
JS has this error too, but nothing forces Java to have this issue if they fix the no-mutable-captures problem. Dart got this right from the start.
Here's JS showing the same issue.
for (var i = 0; i < 10; i++) {
array.push(() => i)
}
array.forEach((fn) => console.log(fn())) // Prints 10 every time.
The correct fix is that each loop iteration gets a fresh i instead of i just being mutated. Currently you can't see the difference in Java between there-is-only-one-i and there-is-a-fresh-i-every-time, so Java is free to do this right.
cess11 262 days ago [-]
Here's what I'd do:
array = [];
function clos(val) { return () => val; }
for (var i = 0; i < 10; i++) { array.push(clos(i)); }
array.forEach(fn => console.log(fn()));
This outputs 0-9 instead of 10, though I'm just a silly self-taught person that never got schooled at these things, maybe there is some reason not to use closures that I don't know about.
ifwinterco 262 days ago [-]
These days in JS you can just use let instead of var and it will work, but in itself that inconsistency is a bit weird
cess11 262 days ago [-]
Yeah, let/const is what I tend to use. Here I wanted to change someone else's code as little as possible.
Edit: Oh, right, it also solves the 10-issue. Thanks, didn't realise at first.
nine_k 263 days ago [-]
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.)
kaba0 262 days ago [-]
The funny thing is, go is significantly more verbose than even decade-old java.
Fire-Dragon-DoL 259 days ago [-]
It's like looking at two very different people and realizing you are attracted by both: each one has their quirks and perks, so you like both.
Might still end up marrying only one though!
dgb23 262 days ago [-]
Expressiveness has many dimensions. It depends on what you are trying to express in the first place.
C++ or Rust are expressive in both directions, towards the metal and upwards in terms of abstractions. But those languages are not easy to reason about. They come with lots of stuff, hard to grok abstractions, ceremony and so on.
Small languages like Scheme or Lua are expressive in that they don't force you to write a lot of stuff to get the job done. But those languages don't typically let you express what the computer should actually do at a lower level.
C or Zigs let you do those things and are also relatively small languages. But their facilities for abstraction are limited and they force you to express more computational details.
Go frees memory and schedules goroutines for you, but lets you express a lot more about how you lay out memory than other managed languages, so you can actually think in terms of bits and bytes. It has a great std lib so you can get stuff going quickly and reliably.
gpderetta 262 days ago [-]
As a C++ programmer, I definitely like my languages with extra legs nailed on.
But sometimes limitations can spur creativity.
ReleaseCandidat 263 days ago [-]
I write less boilerplate in Go than in Rust, about as much in Go (but of course different ;) as in Haskell).
nine_k 263 days ago [-]
That's cool! How do you manage? E.g. the `if err != nil { return err; }` stuff?
ReleaseCandidat 262 days ago [-]
Well, in the context of Lisp, `err != nil` is equivalent to Lisp's parens: if somebody complains about that, chances are their critique is superficial (and there are enough "real" problems when using Go, especially when using goroutines and channels). The amount of low abstraction Go code I write is compensated by the amount of "abstraction boilerplate" like implementing type classes by myself or letting the compiler derive, implementing newtypes, adding GADTs, adding Pattern Synonyms and Type Families to be able to comfortably use the GADTs without much boilerplate and so on. Rust adds for example the need for associated types and is generally more verbose than Haskell.
I do not see a significant difference in the amount of boilerplate needed for catching exceptions, pattern matching or branching on the existence of an error value. Nobody can stop you from implementing `bind` in Go for your error (which I did in some places), but of course Rust "wins" with its `?`.
pjmlp 263 days ago [-]
We get used to being yet another work related chore.
dheera 263 days ago [-]
The "j" is always what put me off from learning Clojure. I've had too many bad experiences with JDKs and JVMs that I stay a thousand kilometers away from any thing that has "J" in it unless it's referring to JavaScript.
rads 262 days ago [-]
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
KingMob 262 days ago [-]
Interesting. Usually the reluctance comes from people confusing Java the language with JVM the platform.
kaba0 262 days ago [-]
Well, there aren’t too many more stable platforms out there, so your experience elsewhere couldn’t be all that cloudless either.
ertucetin 262 days ago [-]
You can also try ClojureScript, which runs on JavaScript. If you're developing frontends.
neilyio 263 days ago [-]
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.
And when you're ready to take the fun to the web, look no further than Biff: https://biffweb.com
zarathustreal 263 days ago [-]
I love this so much. The power of being able to connect a repl to your production instance and fix bugs instantly is hard to understate. It’s actually kind of hard to explain to devs used to deployment cycles measured in weeks
fire_lake 262 days ago [-]
How is the fix committed once you are done? Does this run counter to immutable deployments?
zarathustreal 261 days ago [-]
Do not try to "commit" or "immutably deploy" lisp data/code. That's impossible. Instead.. Only try to realize the truth.
There is no "commit" / "deploy".
packetlost 263 days ago [-]
I recently went through most of the Biff tutorial but found it seemed to be missing some parts later on that had me scratching my head. It was otherwise a very enjoyable experience and Biff seems like a great way to get a "batteries included" starting point for web (similar to something like Rails or Django, though maybe not as comprehensive as either of those).
jacobobryant 263 days ago [-]
Mind sharing the parts of the tutorial you had trouble with, if you remember?
packetlost 262 days ago [-]
I'll see if I can go back and figure out where I lost track of things. I believe it was right around when making the message entry box functional. I ended up having to dig into the repo to see what I was missing.
jacobobryant 262 days ago [-]
Good to know--I'll do another run through the tutorial sometime and see if I notice anything. I know there are a couple things I need to update in the example repo at least.
hi-v-rocknroll 263 days ago [-]
Ah neat. But darn, I was kinda hoping for supporting tools and packages with names like tannen, delorean, doc, mcfly, and strickland ;)
neilyio 263 days ago [-]
It features a time-travel database, if that helps!
I don't know if op had chance to evaluate because they were outatime.
yogthos 263 days ago [-]
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.
yogthos 263 days ago [-]
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. :)
socksy 263 days ago [-]
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.
nathants 263 days ago [-]
the best reason to learn clojure is reagent[1], by far the best way to use react.
shadow-cljs[2] makes using npm libraries easy.
i’ve settled on go backends and reagent frontends as my default setup[3].
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]
I think the writing style is too fluffy, but I realize some people like that. The true sin of this book is that it actually starts by teaching the reader Emacs, not Clojure. That's a huge distraction for a beginner who is probably coming from VSCode these days.
Agreed. Perhaps using VSCode with the Calva plugin if the reader has no previous experience with Emacs.
tastyminerals2 263 days ago [-]
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.
ryan-duve 263 days ago [-]
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?
iLemming 263 days ago [-]
> Do you think learning Clojure would get me something in addition to that
For me, the biggest benefit is that it's hosted. Learning only Clojure, I can easily today write for JVM, .NET, JavaScript, Flutter, or shell scripts. Even when I need to write Lua, I'd usually pick Fennel. It's not Clojure but feels very similar. There are libs that can give you Python or R interop from Clojure. There are projects to target Golang, Rust or Erlang. Jank is a super interesting, experimental implementation of Clojure that runs on LLVM, I'm very excited about it. Do you want to become a true polyglot programmer? You only need to learn Clojure.
ducktective 261 days ago [-]
>For me, the biggest benefit is that it's hosted...I can easily today write for JVM, .NET, JavaScript, Flutter, or shell scripts.
Isn't CL better for this? You are not bound to some VM and you can produce native binaries.
iLemming 260 days ago [-]
I can't really say. I know you can (for example) use LispWorks to write CL for mobile, targeting both - Android and iOS, but I think there would be greater friction to keep it cross-platform, each platform's nuances would have to be handled differently, it's not going to be very unified codebase (I think).
rtpg 263 days ago [-]
I think Clojure leans into immutability a lot, and that leads to more interesting APIs and norms that are valuable. CL has always felt a bit more.... "running on a machine" that I feel has less of a place in an era where everyone and their dog has functional programming essentials built in.
Clojure is interesting, but some Clojure APIs (stuff like Spectre) is "I want this everywhere now" stuff.
didibus 263 days ago [-]
Clojure also teaches you functional programming, where-as CL only teaches you the LISP beauty. Clojure teaches you both Lisp and Fp. So the FP part should still be worth it even though you got the Lisp part from CL.
mikedelago 262 days ago [-]
How does fp in clojure differ from fp on common lisp?
Verdex 262 days ago [-]
Clojure has immutability built into the language and the core data structures are all persistent so that you can get sufficiently efficient partial updates while still preserving a pure FP style.
Furthermore, the data structures in clojure also have interfaces that make it easier to swap out which data structure you're using while still keeping whatever map/filter/reduce algorithm implementation you're sending it through.
Common lisp, on the other hand, has setf. Which more or lets you mutate anything. You certainly can code in an FP style in common lisp, but it doesn't restrict you in any meaningful way. Not a problem if you control the entire codebase, but when gluing components together this can be a source of friction.
didibus 261 days ago [-]
FP in CL is kind of optional, and it's also more in the sense that you can pass functions as arguments or return functions, meaning FP == First Class Functions.
But in Clojure, FP is kind of mandatory, there's no OO for example, and it's more than just first-class functions, it's also immutability.
tmountain 263 days ago [-]
Depends on your style of programming CL. Clojure is immutable by default with a heavy emphasis on concurrency and strong ties to the JVM as its host environment. The differences you’ll find mostly lie in those areas.
epgui 263 days ago [-]
IMO, Clojure is superficially/syntactically much more elegant and beautiful than Common Lisp or Scheme. I find this really helps you focus on what matters most.
Clojure uses immutable lists, vectors, hashmaps, and sets. Also it has syntax sugar such as [1 2 3] for vectors, {"Jan" 31 "Feb" 28} for hashmaps, #{1 2 3} for sets, and much more. Also it has multimethods which are even more generic than CLOS.
phendrenad2 263 days ago [-]
Is there a version of Clojure that compiles to LLVM or something? Sort of like what Scala-native was supposed to be (but never materialized). I like functional programming but I can't abide the Java ecosystem in 2024, it's just too archaic for my taste.
jomendoz 263 days ago [-]
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.
baq 263 days ago [-]
the top comment as of writing this reply points to https://babashka.org/, but it's GraalVM instead of LLVM (which shouldn't really bother you)
kolme 263 days ago [-]
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.
Clojure is becoming popular in Mexico, and I'm guessing Brazil may see a similar situation, because of nubank. It is a popular destination to go work there for computer scientists, and they seem to work mostly in Clojure. I seem to remember they even employed some of the core members of the language? I think at some point they also bought the company of Jose Valim (Elixir's creator), but I think they are still mostly a Clojure shop.
hlship 263 days ago [-]
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.
zachromorp 262 days ago [-]
I often encounter Clojure Chinese users (at least Chinese sounding names, don't know about their nationality). I wonder if Clojure is a thing in China ?
zerg000000 261 days ago [-]
no. But because of the population, even 0.0001% of developers using it, it still has an observable impact on the community.
hombre_fatal 263 days ago [-]
Maybe online "learn lisp" repls should implement paredit keybindings in their editor and have a short section on how to manipulate s-expressions. Because you're gonna have to learn it to use the language, but it also helps people understand from the start that "oh, so using the language isn't actually complete shit".
Instead, this fact always seems glossed over, and because of it anyone who spends 60 seconds writing lisp by hand into a repl assumes that's the lisp experience and nopes out forever. When in fact paredit makes lisps the easiest-to-edit languages in the world.
writeslowly 263 days ago [-]
I spent a few months writing a decent sized Clojure program just using my IDE's matching bracket highlighting and some sort of rainbow parentheses settings, and never found it very annoying. I'm not sure there are that many more parentheses than curly-bracket languages, it's just that all of the closing parentheses are more likely to cluster together at the end.
jwr 263 days ago [-]
> When in fact paredit makes lisps the easiest-to-edit languages in the world
That is true and whenever I switch from Clojure to C, I immediately feel the pain (what do you mean I can't just kill or lift this entire expression?).
But structured editing has a learning curve, so it's a difficult balancing game. Beginners are overwhelmed by all the parentheses, while experienced programmers don't even notice them and love the structured editing approach.
globular-toast 263 days ago [-]
At the very least they should have auto-closing and matching parens. I dread to think how many people have been put off Lisp because they think we actually type all those parens (in fact Lisp users have enjoyed IDE features many programmers could only dream of for decades).
anentropic 262 days ago [-]
I remember trying Clojure a few years ago, and Racket, and had similar problem with both... feels like everybody doing LISPs is using Emacs and it was hard to get a decent set up in VS Code configured and working properly
askonomm 263 days ago [-]
Or Parinfer. I personally like Parinfer more than Paredit, mainly because I can't be arsed to learn a gazillion keyboard shortcuts.
hombre_fatal 263 days ago [-]
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).
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.
askonomm 263 days ago [-]
I'm one of those mainstream devs (who also did Clojure for 6 years, coincidentally) who never got into emacs, and always just used the Cursive IDE plugin for IntelliJ and its Parinfer editing mode. I know I probably didn't unlock true god mode of productivity, but it worked fine for me.
hombre_fatal 263 days ago [-]
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.
stefcoetzee 263 days ago [-]
Yup, Calva's pretty great.
simongray 262 days ago [-]
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).
silcoon 263 days ago [-]
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.
neilyio 263 days ago [-]
I use Helix [0] myself, which has tree-sitter based commands for moving + selecting up/down/forward/back by expressions. These are built-in and require no configuration.
It's surprisingly excellent! Sure, the "language" of paredit features more powerful text manipulation that just simple movement... but combined with the new "jumping" in the latest Helix release [1], it makes for a very impressive keyboard-based navigation system.
I still remember Professor Brian Harvey rolling out his terminal on a cart in Berkeley's CS61A and typing out commands in a scheme repl. Learning what a y-combinator was with Structure and Interpretation of Computer Programs, and building my own scheme compiler with scheme.
bhasi 262 days ago [-]
Oh wow, I didn't know that "Y-Combinator" was not just a fancy name but was an actual computer science concept. Thanks!
Nice catch. Is it an issue though when the script injection only runs within your own browser session?
Jeaye 262 days ago [-]
No. That in itself shouldn't be a cause for concern. Local users can do anything to their own machines already. It would be a concern if you persist this to then later be loaded by someono else's machine.
munirusman 263 days ago [-]
Shameless plug: If you’re looking for an online compiler specifically designed for conducting interviews in Clojure, you might find [0] very useful. It’s designed to streamline coding interviews with real-time collaboration features.
I wonder why no functional language has the degree of success imperative or OOP languages have.
Is functional programming too weird for majority of computer programmers? Is functional programming not optimal for solving industry problems? Maybe there is no functional programming that that is up to some set of standards? Is functional programming too complicated?
I do enjoy functional programming myself, and while I don't use a functional programming language at work, I try to use functional programming paradigms when I finds it suits the probem.
dgb23 262 days ago [-]
There are degrees and different shades of "success" and "functional programming".
Clojure is successful, stable and well maintained. However it is a niche language compared the big mainstream languages. It takes a functional approach by default, but doesn't shy away from other paradigms/patterns/idioms when appropriate.
Functional programming itself has had a huge influence on mainstream languages since about a decade and a half. More and more pragmatic features have been introduced in mainstream languages. Newer languages have adopted functional idioms from the get go. On the macro level as well, ops and architecture have been adopting statelessness, reproducibility and so on.
The consensus has shifted towards containing state. The more moving parts you have, the harder it gets to reason about a whole thing. Treating data as data.
OOP has also changed and got refined over this time. I think people realized that it has good ideas (generic interfaces, polymorphism etc.) and bad ideas (inheritance, local state etc.). Same thing with FP, there's stuff that's esoteric and too far from the reality of actual programming and stuff that is pragmatic and simplifying in there.
cageface 262 days ago [-]
Functional programming languages haven't gone mainstream but functional techniques are very common now.
Higher order functions, immutable data structures, declarative UI frameworks, etc are things you're quite likely to encounter in a contemporary codebase these days.
DeathArrow 262 days ago [-]
That's what I said. I do use functional programming techniques, I just wonder why functional programming languages don't see a larger following.
cageface 262 days ago [-]
Good question. I suspect it's because the downsides of losing the larger ecosystem of more mainstream languages outweigh the upsides of a functional language. The libraries, package managers, tooling, runtime, debuggers, documentation, and editors etc just aren't anywhere near as mature for Haskell or Ocaml as they are for Typescript or Python, for example.
nequo 262 days ago [-]
Debugging is a known sore point with OCaml for example. But what is the problem with the Haskell RTS or the OCaml runtime? Why is it not “anywhere as mature” as Python’s or something like Node for JS?
Python’s had the GIL while Haskell has had a well functioning parallelized runtime for a long time. And both Haskell and OCaml’s runtimes outperform Python’s and are on par with Node.[1]
As for documentation, every time I come back to Hoogle,[2] I remember how I miss it in other languages that just don’t have anything similar going. The lack of types doesn’t help, and reading the documentation of the Python standard library or a popular library like torch is sometimes like solving a puzzle because it’s unclear what a function or a class constructor really does or even what class’ instance it returns.
I didn't get anything close to that from your original message. I thought you meant "I write functional JS, but am wondering why a language like clojure or OCaml isn't more popular"
freilanzer 262 days ago [-]
Inertia of languages like Java, C, C++, C#, etc. due to university and existing projects. Also managers wanting a huge recruiting pool.
yiyu_earth 262 days ago [-]
[dead]
tmtvl 263 days ago [-]
So here's something which I, as a Lisper, don't understand: Clojure has syntax for hash tables (the curly braces), why doesn't it use those for let-bindings, instead choosing to use vector syntax? (Of course I prefer standard Lisp syntax which can easily be extended for multiple-value and destructuring bindings:
(let ((quotient remainder (floor x y)))
(+ (* quotient z)
(* remainder (floor z y))))
Where floor is a function which returns multiple values rather than a list or vector of results?
Also, but this is entirely on me being an absolute dullard, I like having the brackets around each binding as a kind of guardrails for my mind to not lose track of where I am and what belongs where.
didibus 262 days ago [-]
It's just:
(let [[quotient remainder] (floor x y)]
(+ (* quotient z)
(* remainder (first (floor z y)))))
Because in Clojure there's no multiple value bind, when you want to return many results from a function, you wrap it in a vector, and that means you than just destructure that vector. That also means, it's not smart enough to know if it's used in a single value context or multi value context, so you need to always get the value out of the vector, which is why I call first in that second call to floor.
camelspotter 262 days ago [-]
this example shows how "elegant" closure treats syntax errors, almost looks like a bad joke.
That's a great introduction, but... how to do an if statement would've been cool to add in the tutorial.
263 days ago [-]
613style 263 days ago [-]
What is this "statement" you speak of?
(if (= 2 2) :OK :nope)
makach 257 days ago [-]
see, that was missing from the tutorial, or did I miss it? :ok and :nope - are those valid keywords or functions?
drpossum 263 days ago [-]
I love clojure, but it's really lost a lot of the momentum it had as many imperative languages have adopted parts of functional programming (and much for the better) over the last many years.
On the other side I've felt a lot of the ecosystem work that was done has a more "timeless" quality. Coupled with java interop I haven't ever felt wanting when I do reach for it for some hobby projects I keep up with.
One thing I will say, since it takes a different tack and philosophy, I think any programmer learning some of it benefits from the different perspective.
d4mi3n 263 days ago [-]
In my experience, the subsets of the community around clojure were one of the biggest problem with it. It's gotten better over the years, but circa 2016 - 2018 it had a spike in popularity despite being very hostile to newcomers.
There was an almost apologist attitude towards rough edges or failure modes folks new to the community would fall into. I managed to work with it for a time despite that, but it left me with a bad taste in my mouth. It's really important to make your ecosystem and communities welcome to new users or they're destined to fade.
crispyambulance 263 days ago [-]
> ...had a spike in popularity despite being very hostile to newcomers.
To be fair the community itself, as humans, was very welcoming to newcomers. Really super nice and helpful folks.
If anything was "hostile" it was the dominance of emacs and very limited options for other tooling. That, and the horrifically unhelpful leaky abstractions in the stack trace when something went wrong. It's a big step, if you don't already know emacs to learn emacs (at a fairly advanced level) AND a new very different programming language.
I see now there's a vscode option with Calva. I haven't tried it. Might do that someday. Really wanted to like this language!
neilyio 263 days ago [-]
I'll second that the popular tooling, while powerful and reliable, its just not user friendly.
The official Clojure CLI, for example, is just plain confusing, and that's most people's first impression to the entire ecosystem. The config files, while they use the wonderful `.edn` format, are also not intuitive. Newcomers are the most likely people to encounter the most amount of error messages... which are famously difficult to read.
And that's before you even get into REPL configuration, which involves coordination with your editor process, a client process, and a server process. Even if you have a tool like Calva or CIDER managing it for you, you'll still get confused when something goes wrong unless you grok all the moving parts.
Even if you figure that all out, you still don't have Parinfer or equivalents setup yet in your editor. Also, clojure-lsp tends to require some configuration to get working the way you want. And that's before you get started with ClojureScript, which brings the complexity to another level entirely.
Despite all this, I love Clojure. It's an expert's language, even though it shouldn't have to be. Once you learn this stuff, you respect why much of this complexity exists. It's inherent to the amount of power the language gives you.
But doesn't mean we can't make it easier to use and get started with.
roenxi 263 days ago [-]
> The official Clojure CLI, for example, is just plain confusing, and that's most people's first impression to the entire ecosystem. The config files, while they use the wonderful `.edn` format, are also not intuitive.
I completely agree; it is unfortunate they don't spend more time officially recommending leiningen.org. A beginner attempting to use the built-in CLI is going to lead to a poor first 3 months.
astrashe2 263 days ago [-]
This matches my experience as an amateur programmer. The initial hill was steep, but I don't think it was because people weren't friendly.
There is one cultural thing that might be confused with unfriendliness. Sometimes people react badly if someone posts incorrect information. But I think that's good. When you search for information about Python or PHP you have to wade through quite a bit of junk. Ironically, it's sometimes easier to find correct answers for Clojure.
Clojure itself is very clean and consistent, it's got a lot of polish to it, which makes it comparatively easy to learn. And there isn't that much of it. But for a long time the tooling was hard.
That's far less of a problem than it used to be. deps.edn and shadow-cljs both made things easier, as has Cursive. People say nice things about Calva, but I don't know it.
I'm a big fan. Babashka alone is enough to make learning Clojure worthwhile. Also, for someone like me, it's kind of nice that it feels almost finished. Once you learn it, you know it, and now that the tooling has settled down a bit you don't have to keep running to keep up.
hluska 263 days ago [-]
People posting the wrong information is a great opportunity for a community to explain why it’s wrong. We lost that take on community and it’s a major loss for our entire industry. It’s remarkably hard for people to figure out why they’re wrong when they’re wrong.
I don’t think the solution is the Usenet-esque “you are wrong and your breeding is suspect” way. But there’s a very good place in the middle and I’d really like to find that place.
Business wise, I think we’re using the wrong paradigm in some major places. Maybe we can beat that while I’m still alive and that would be a net win for our whole craft.
yungporko 263 days ago [-]
the tooling is the reason i gave up when i was interested in learning clojure. everywhere pretty much said that emacs was the "correct" editor to use for clojure, i tried it out for a bit but ultimately learning a new language and a new editor at the same time was just too big of an ask.
i tried calva with vscode afterwards but it was basically the same thing, calva has paredit which overrides the keyboard navigation and there was no way to disable it. somebody had asked for the option to disable it on the github but the creator said something along the lines of calva without paredit wasn't the way he wanted it to be used, so i just picked another language to learn.
cospaia 261 days ago [-]
Wow. I'm the creator of Calva and I can tell you that I do not have an opinion on how to use it. I would never say something like that, and now I wonder what I could have said that was interpreted this way...
By default Calva Paredit overrides three or so keyboard shortcuts. This is to help the users to not accidentally delete brackets, which in turn keeps the support questions down about broken Clojure code. I have tried to make it super easy to disable these overrides. There's a command for it. There's a button in the toolbar for it, and there's a setting for it.
There's also a setting for removing all Paredit bindings, even those that are not overriding any default ones. (This setting is for people who use Paredit, but want to provide their own shortcuts entirely.)
if there's now a way to disable paredit entirely then i might check it out again some time in the bear future
gleenn 263 days ago [-]
Cursive plugin for IntelliJ is a fantastic option, it's not free but it is excellent.
aeonik 263 days ago [-]
it's nice, but I used to write Java, and even getting IntelliJ setup on my laptop took a lot of fiddling with JVM versions and getting it to recognize my Clojure install.
To this day, I will still have weird issues where Cursive won't run a project, and Emacs will.
All that being said, I do really like Cursive.
cess11 263 days ago [-]
I use https://sdkman.io/ to manage JVM versions, have you tried that? I haven't used Cursive, maybe it does something weird, but in general IntelliJ seems to accept it just fine in my everyday work.
ARandomerDude 263 days ago [-]
clojure-lsp and clj-kondo work with any editor and are both excellent.
A somewhat similar thing I ran into is that existing Clojure code can be incredibly difficult to grok if you don't already know the language very well.
Some of this comes from lisp's minimalist syntax, which makes it hard to even know what kind of thing you're looking at when you're encountering a new thing for the first time. But the problem is also compounded by some of Clojure's more distinctive (and powerful) features such as ad-hoc polymorphism and using maps to pass function arguments.
The closest analogy from imperative languages that I can think of is another language that's famous for being incredibly productive in the hands of a skilled user, but whose code tends to feel kind of write-only: Perl.
kazinator 263 days ago [-]
In Perl you have the dynamism, and the syntax.
Evidently, 93% paint splatters, when OCRed, are valid Perl programs:
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.
263 days ago [-]
slifin 263 days ago [-]
If I can get clojure storm running in my project then I find a lot of code incredibly straightforward
Because there's a preference for just passing immutable data around the time travel debugger really helps me understand complex interactions
kimi 263 days ago [-]
YMMV. I always found it very welcoming and full of interesting people.
bunderbunder 263 days ago [-]
I may be wrong, but I think the parent poster was talking more about the language and ecosystem than the community. I, too, have found the community to be amazing. But the language itself used to be quite frustrating to learn. It's better now, but I think that by the time it got fixed many companies and teams had already been burned.
Clojure is particularly vulnerable to that because it really is an enterprise applications language. That makes it more vulnerable to learning curve problems than a language like Rust whose most direct competitors are other languages with comparably steep learning curves.
kimi 262 days ago [-]
Yes, I agree with you, learning is complicated, because it's mostly un-learning habits you had for 20+ years. Stack traces suck, but after a while you grok them. The clojure/core is full of niceties, but it's so big you need a map just to know what's in there. And still...
262 days ago [-]
wredue 263 days ago [-]
That is basically every functional programming language in existence.
calvinmorrison 263 days ago [-]
> many imperative languages have adopted parts of functional programming
nailed it. I don't know much about functional languages other than a haskell course back in the day, but I don't want to do functional all the time. It plays great in some situations and I am happy to use those language subsets in my daily coding, but I don't need my entire application to be written in an esoteric language
grumpyprole 263 days ago [-]
This is of course a popular opinion and pretty much the C++ philosophy. Unfortunately a lot of the benefits of functional programming cannot be fully realised unless you are "all in". The paper "The Curse of the Excluded Middle" is somewhat blunt but it makes some good points: https://queue.acm.org/detail.cfm?id=2611829
macintux 263 days ago [-]
Programming without immutability is so painful to me, no matter how many functional features a language adds.
throwaway96666 263 days ago [-]
In my experience jumping from a Clojure shop to a large Java shop a few years ago, the benefits of persistent data structures are overstated. Mutable collections are just as good for 99% of use cases. And they're a lot faster (in single threaded code) and occasionally mutation makes things easier.
The selling point of Clojure is that persistent data structures prevent several classes of bugs (unintended mutation, locking, etc.). But in reality -- as long as your team members are good enough programmers -- I don't see these kind of bugs happen in practice.
That being said I love Clojure and the standard library is the best out of any language. It is a great choice in the small market of "projects that need simple and correct code".
slifin 263 days ago [-]
Looking at an object in my debugger back in my call stack and assume I'm looking at the value as it was but in reality it was mutated in a subsequent stack gives me trust issues
It's probably fine most of the time it's just when I have to get into the weeds I want to have stability
throwaway96666 263 days ago [-]
That seems like a debugger problem, can't the debugger clone/maintain history of the object?
grumpyprole 263 days ago [-]
This is like saying "the benefits of functional programming are overstated". If you are happy with mutable collections, you've happy with mutable variables, pervasive side effects and essentially the status quo. Many of us are not happy with the level of software quality out there and the status quo, Rich Hickey included.
iLemming 263 days ago [-]
> the benefits of persistent data structures are overstated.
Well, what else is overstated?
- Structural editing? Fine.
- REPL-driven development? Okay, let's throw that out the window.
- Hosted nature and the interop? Gone.
- Destructuring? Eh, we kinda have it in Javascript, right?
- Concurrency support? Who needs that shit, anyway, right?
- Simplicity and elegance? Arguable. Some like verbose Typescript code more.
- Functional programming? What the heck is it even?
The point I'm trying to make is that you can't just "remove" an essential part of what makes a language. Rich Hickey took a year-long sabbatical (or was it two or even three years? I forgot) and used his savings to get this aspect of the language right. Without the immutable collections, the language would've been an entirely different beast.
grumpyprole 263 days ago [-]
It's painful for the compiler too, it has to turn the parents imperative code into SSA (essentially A-normal form) so it can optimise it!
wredue 263 days ago [-]
You guys can make all the baloney claims about this you want.
The day that Haskell stops being 10x slower than languages providing mutable data is the day we can start to seriously entertain your claims.
joshlemer 263 days ago [-]
I think the persistent collections are generally worth the cost. IIRC, for HAMT maps/sets for instance, they're about twice as slow for reads and four times as slow for writes. For bulk updates, you can also slip into using transients, and then when you're done, "freeze" it back into a persistent data structure.
I only wish that the transient collections would support more different kinds of writes, like support all operations on java.util.List/Set/Map kind of thing. Forget which ones aren't present but I remember there being a couple...
grumpyprole 263 days ago [-]
There's nothing baloney about SSA form. I'm sorry to say it, but C is just not close to the metal any more. Even if you are able to hand optimise something so it works well on one architecture, it likely won't be optimal for another.
As for Haskell, it does pretty well being 100 times faster than Python with 100 times less the number of people working on it.
wredue 263 days ago [-]
I believe that your interpretation of what’s actually happening is what’s baloney.
The fact is that you Haskell people only talk about the optimizations that can sometimes open on immutable data in specific circumstances. What you generally ignore is the optimizations that immutable data permanently locks you out of with no recourse.
As with most things programming, immutability should be considered a tool, not a rule.
grumpyprole 263 days ago [-]
> I believe that your interpretation of what’s actually happening is what’s baloney.
You seem to think that functional programming and in-place updates are mutually exclusive. This is not the case, e.g. Haskell supports mutation as a tracked and controlled side-effect. It can even give static guarantees that a function is pure even it uses mutation internally.
Recent research even suggests that compilers can add the in-place updates for us: https://www.microsoft.com/en-us/research/publication/fp2-ful...
wredue 260 days ago [-]
I don’t really care which functional programming advocate you’re quoting. They’re all liars when they make these claims.
You can say SSA, static guarantees, internal mutability, blah blah blah all you want. When third party, not specifically chosen anecdotes to make FP look good, measurements stack up to the claims, we can have a better conversation.
It’s not looking good though, cause these claims of “actually, Haskell is faster than C because compiler magic” have been going on since well before stable Haskell like 15 years ago, and they’ve been lies for just as long.
259 days ago [-]
wredue 263 days ago [-]
>a lot of the benefits cannot be fully realized unless you’re all in
That is working under the assumption that “the benefits” are actual benefits.
IMO, “the benefits” are measurably drawbacks, and not a technical debt I am willing to accept for no good reason.
grumpyprole 263 days ago [-]
Functional programming is basically maths, it solves the problem of how to compose software safely. I struggle to understand why anyone who really cares about their craft would not want this. In fact, many want it so badly they are willing to make sacrifices for it (performance, esoteric languages).
elamje 263 days ago [-]
Should have maintained a python interoperable runtime. Less and less people use JVM languages for things like web apps, data eng, data science, ML, etc.
in my opinion competes with python when it comes to DS/ML. I find it a lot more comfortable to use if you use emacs bindings
winrid 263 days ago [-]
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)?
rads 263 days ago [-]
This is a good starting point if you want to get a web app going quickly:
https://biffweb.com
People don't really use ORMs in Clojure, they just write SQL directly and abstract the details from consumers using functions. That said, HoneySQL is a common alternative to writing SQL that makes it a lot less painful (and composable!):
https://github.com/seancorfield/honeysql
neilyio 263 days ago [-]
HoneySQL is so, so good. It does for SQL what Hiccup did for HTML. Once you start writing with those, it's hard to remember there's any other way.
zerg000000 261 days ago [-]
Totally agree! or datalog is even better if you can accept not using the mainstream databases.
winrid 263 days ago [-]
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.
rads 263 days ago [-]
Here's a quick example of how DB access generally looks in production:
(ns rads.sql-example
(:require [next.jdbc :as jdbc]
[honey.sql :as sql]
[clojure.string :as str]))
;; DB Table: posts
;; +----+-------+
;; | id | title |
;; +----+-------+
;; | 1 | hello |
;; +----+-------+
(def ds (jdbc/get-datasource (System/getenv "DATABASE_URL")))
(defn row->post [row]
;; This is your optional mapping layer (a plain function that takes a map).
;; You can hide DB details here.
(update row :title str/capitalize))
(defn get-posts [ds]
;; Write a SQL query using Clojure data structures.
(let [query {:select [:*] :from [:posts]}]
;; Run the query.
(->> (jdbc/execute! ds (sql/format query))
;; Convert each raw DB map to a "post" map
(map row->post))))
(println (get-posts ds))
;; => [{:id 1, :title "Hello"}]
winrid 263 days ago [-]
You would abstract away the jdbc/execute part though, right? otherwise that's terribly unproductive compared to django etc.
rads 263 days ago [-]
In practice I do wrap `jdbc/execute!` with my own `execute!` function to set some default options. However, there is no ORM layer. What makes you think the code above is terribly unproductive?
Edit: Not trying to dismiss your concerns, by the way. In Clojure you can often get away with doing less than you might think so I'm genuinely curious about the critique.
winrid 263 days ago [-]
In Django I would just do Posts.objects.filter(title=x)
I don't need to define driver boilerplate for every query (or ever have to write it... at all).
didibus 262 days ago [-]
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.
It's 95 loc and that includes the templates. There's no framework.
winrid 262 days ago [-]
> I implemented the small website you were talking about
Thanks, that's neat.
I'm not even talking about the framework part. Just db access. Let's say I have a Posts with a managed_by property that points to a list of User which have a ManagedProfile. In Django's ORM (or any good ORM), I could do:
if post.managed_by.contains(user.managedProfile)...
or I could do:
post.managed_by.add(user.managedProfile)
also all these tables and join tables are generated by just a few lines of model definitions.
I'm still in control. I am writing the code. I get to choose when I do slow and fast stuff. Not having these features isn't "more control" it's less features. :P
I still see the benefits of Clojure, though!
didibus 262 days ago [-]
Ya, so you'll be working with the DB directly instead of through an Object representation.
And I agree with you, it's less features, and maybe it would be nice to have something similar in Clojure, but there's a reason the feature doesn't feel as needed, and nobody bothered building it.
In OO langs, one of the major pain points the ORM solves is mapping the result back into an Object. Otherwise, you have to manually go:
post = new Post();
post.title = queryResult[1];
post.content = queryResult[2];
...
In fact, some ORM keep to that only, I think are normally called micro-ORMs. All they do is data mapping to/from between the DB and your object model.
In Clojure, you don't have this pain point, the DB query returns a list of maps, and you work with those maps directly.
I suspect this is the main reason why no one bothers implementing an ORM-like in Clojure.
That means, for your example, you would create a function that queries the DB to check if a post is managed by a particular user. And you'd call that for your condition:
(if (is-managed-by? post-id user-id) ... ...)
Or to add one you'd do something similar, create a function that adds a user to manage a post:
(add-manager-to-post post-id user-id)
You're working directly with IDs, because there are no Objects here. You're working with data directly, and that data is similar to the data in your DB, the representation is much closer between what your code uses and the DB.
That means, in OO langs, you think of your state as being those object models, and then you try to sync them back/forth to the DB, after you've mutated it a bunch, you call .save() on it for example. But in Clojure, you think of your state as the DB itself, if you want to change state, you just run a query on the DB to change that state directly, you don't modify some in-memory model and then try to sync that back to the DB.
rads 263 days ago [-]
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.
didibus 263 days ago [-]
There's no object in Clojure, so there's no need for an Object Relational Mapper. You just work directly of the query result sets, which the SQL library itself can conveniently turn into rows of maps if you prefer (over rows of lists).
winrid 262 days ago [-]
No object is kinda mind blowing since it has java interop. I guess everything just becomes maps?
didibus 262 days ago [-]
Well, there are objects through interop, and under the hood everything is compiled into one. But when you develop an app, you won't be defining classes and instantiating objects of them, you'll be instead writing functions that return maps or other data-structures.
263 days ago [-]
whalesalad 263 days ago [-]
There is not a batteries included framework a-la Django or Rails for Clojure. I would argue that is one of the biggest pitfalls to the language/ecosystem. If you are building a web application, you will need to invent the entire thing yourself. There are micro frameworks and tools that exist standalone to deal with HTTP, DB ORM, etc... but that will be an exercise left to the reader.
Some will say this is a good thing (and as a very experienced engineer I will agree) but the barrier to entry for a newbie is quite high in this regard. You have all the rope to hang yourself with - and you will. Especially if you are on a team full of lots of interns and junior folks.
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)
alabhyajindal 262 days ago [-]
Isn't it tiring to type so many parenthesis all the time? This is probably my first time encountering a Lispy language, other than Logo.
puredanger 262 days ago [-]
The number of parentheses is the same, they're just in a different place. Isn't it tiring to type so many semi-colons and commas all the time?
alabhyajindal 262 days ago [-]
I think it's tiring because before every expression I have to type a (. It's not tiring to type semi-colors and commas because they are very well positioned on the keyboard and I don't have to reach out in the same way as I need to for the parenthesis.
Plus, there are not a lot of semi-colons and commas in JS and Python.
stefcoetzee 260 days ago [-]
Replace in Python with 4-space / tab indentation.
phforms 262 days ago [-]
I know a lot of people think that you write more parentheses in Lispy languages, but actually in most cases you just type them in a different order, e.g. instead of `foo(x, y)` to call a function, you type `(foo x y)` and you even save a comma.
Of course, there are constructs such as `let` and `cond` that are more parenthetically noisy (not so much in Clojure though), but on the flip side you don’t have to remember a lot of special syntax like in non-lisp languages.
Most Lisp-people also use structural editing tools like paredit[1], which make it really easy to write and edit s-expressions. I found that after some time I didn’t really think that much about parentheses anymore.
This looks overwhelming for someone who hasn't written any Lisp. My thought looking at this code was that I have to type a parenthesis before formulating any thought. Which is crazy because it's not some huge thing. It's like saying I have to press Enter before writing a line of Python.
masspro 263 days ago [-]
<hot-take type="anecdata"> My Clojure experience was that basic dev experience things were shockingly behind where any other moderately popular lang is at. It's been some time though so all I remember clearly is bad error output. Stack traces are hard to read unless you install $random_lib. But worse than stack traces are type errors/"Java errors": if you give the wrong args to a function, the error output is completely inscrutable, generally a very short string like `java.lang.Foo does not implement IBar`, which is only helpful if you kind of know how the Java layer works and all your args are different enough types that you can guess which one it's talking about (bonus: anything function-like is just `IFn` so good luck). Ahhh and that made me remember: the doc situation even for stdlib is bad. Everyone around me used a third-party site clojuredocs.org which has broken-formatted auto-ingested versions of the plaintext official doc strings, because it is still the least bad option. No one has decided on a docstring format. No one has decided on code stylistic things either, which has mostly precluded the existence of auto-formatter tools.
The lang itself is good and I recommend folks use a LISP sometime. I was just genuinely surprised a 17-year-old lang was lacking in these areas, and I'd be pretty careful about setting out on a long-term project with it, unless all of those things have radically improved from 6 months ago.
(And like other folks said, you genuinely need editor integrations to not be wasting all your time on pren balancing. Not clj's fault, just LISPs in general.)
neilyio 263 days ago [-]
I'm an advocate for Clojure, and while I believe that the usability speed bumps are more than made up for, I completely agree with you. The awkwardness of Clojure's errors, REPL experience, and build tooling is a dealbreaker for many.
A couple years with Rust has taught me that intuitive errors and tooling will funnel you far enough into language to get you productive, and then you're much more likely to stay. There's just no way I would have stayed long enough to be a Rust professional if it hadn't been for cargo and rust-analyzer.
These "non-language" components of Clojure are just not easy enough to use, and its inhibited Clojure's growth. If, however, you do put in the time to grok these parts, the joy of using the language itself never fades.
joshlemer 263 days ago [-]
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.
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?
puredanger 263 days ago [-]
You don’t use the ISeq interface directly, you use them through the clojure.core API. The seq abstraction is documented at https://clojure.org/reference/sequences
joshlemer 263 days ago [-]
It’s rare for an application developer to need to use ISeq, but library authors do use of when they want to implement custom seq’s right? For them, and also just for those curious to understand how the core interfaces work, it’s still better to be explicit and write what the contract is I reckon.
puredanger 262 days ago [-]
Generally, custom seqs (rare) are implemented by leveraging something like `lazy-seq`, so library authors are also not using it.
Yes, it would be good if there were javadoc on more of the impl, but this is just not an issue for the vast majority of devs.
spronket_news 263 days ago [-]
this is super neat! how did you set up your fonts folder? what tooling did you use?
stefcoetzee 263 days ago [-]
Fonts included in the repo. [0]
Tooling is a combo of Shadow CLJS and Clojure CLI.
Clojure is a really interesting and well designed language with bad error messages and bad official tooling. It feels very sloppy.
Compared to Go, it has a weak stdlib, is more memory intensive, and generally slower. You get beautifully concise code but it can be very hard to follow (or return to after time away).
Go can look completely idiotic or unbelievably focused and practical, depending on the light. It is painful to give up Clojure’s very strong selling points but I find alternatives to be more pragmatic.
hpeter 262 days ago [-]
"weak stdlib": You can use the Java ecosystem, most of the things you need are there
"it can be very hard to follow": I agree, if you are not a lisp developer it's really hard, but if you develop clojure everyday, it's as easy as anything else.
The answer is usually clean code when it comes to clojure. Keep your functions small.
"more memory intensive, and generally slower": yep, the JVM is more memory intensive than Go. No surprise there and Clojure adds up on top of that. Startup times are pretty slow too. But compared to python it's still fast. Apples to oranges.
I would say Clojure and Go are both great languages that tackle different problems so it's not a fair comparison.
BaculumMeumEst 262 days ago [-]
Having projects that make significant usage of interop feels kind of gross, but I guess that is an option yeah. Pig-latin java is probably less fun to write than Go.
Regarding difficulty reading code, I'm not talking about syntax, I'm talking about the fact that many libraries define their own DSLs and you _cannot understand what they are doing_ by reading the code, you have to dig into the library internals to make sense of how they actually work, particularly when documentation is lacking or outdated.
Many problems can be solved by either language, and I wish it was easier to justify reaching for Clojure.
stoica94 261 days ago [-]
If you ever need a clojure boilerplate to start a SaaS product or a startup, there is https://shipclojure.com
Anduia 263 days ago [-]
I found the red over gray combination a bit hard to read, so I checked it with a color contrast calculator:
The contrast ratio here is 3.9:1 (text #DC2626, background #E5E7EB), and the minimum recommended for small text is 4.5:1.
Sorry to be that guy :) I am enjoying the tutorial.
talkingtab 263 days ago [-]
I know many people will have problems with this post, but I am posting as information for clorjure-ophiles. Not reasons, but personal reasons why I don't do Clojure and will not try it.
1. When I read about Clojure (repeatedly) I like it. I am especially interested in transducers. I even built a half baked transducer engine in JavaScript using generators. There is clearly something here of value here. Any system that can produce transducers is worth learning.
2. I spent too many years of my life on Java. It was okay until someone wrote a thing in php (!!) in a day that would be have taken at least a month in Java. Maybe two. I understand the "good" of Java, but for me personally, the cost of that good is just incredible tedium. This is personal and I understand that.
3. However, as soon as I get to the part of installing Clojure where you install a JVM, I start hearing voices that say "It ain't me babe" and "Just say no to Java". PTSD?
For a while there was Clojure-script and I started working with that, but it seemed to fall off the edge of the world.
I will happily try Clojure again if it will compile itself or if it does not require the installation of a JVM. [ And last but not least of irrational reactions- anything associated with Oracle is to be avoided like the plague. sorry]
[Edit: then I saw the stuff about Babashka so I will give that a try]
jwr 263 days ago [-]
Running on the JVM is one of the best things about Clojure. The JVM is an impressive piece of engineering where uncounted bajillions of man-hours were invested into making a good and performant VM with modern GC.
I've been using Clojure heavily for the last 9 years or so and I can't see any reasons to dislike the JVM. Also, I barely ever touch any Java. You don't need to.
delegate 263 days ago [-]
I can hardly write a correct Hello World in Java. I can read it, I can probably write it, but I've never really used it.
Yet I've been writing Clojure daily for the last 9 years.
This means that you really don't need Java in order to use Clojure.
Coming from a C++ background, I used to dislike the JVM out of principle just like yourself, especially since it needs to run on my local machine and it uses so much memory and is slow to start up.
However, once your app is started, you're in the REPL and that's the only time you need to start your app.. You can keep developing for days without restarting your app once.
Once I finished developing my app and I deploy it to a server, I'm kind of happy it runs on the JVM - that thing is super tuned, very fast and runs on a myriad of hardware platforms.. I don't have to spend one minute thinking about those details.
So while the JVM is somewhat inconvenient on the dev machine, it helps a lot when you deploy it to production.
Things became a lot simpler when I stopped worrying about it and just used the language for its power and beauty.
owenmarshall 263 days ago [-]
And if you do need to touch Java for reasons Clojure doesn’t impose, the interoperability story is really good.
I’ve done this with internal libraries - it’s easy to get them pulled into a Clojure code base, wrap them in such a way that the ergonomics of the rest of your code aren’t ruined, and still treat them as a first class citizen for stuff like your build system, artifact stores, etc.
puredanger 263 days ago [-]
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.
jwr 259 days ago [-]
Yes — and thank you for this work! Java functional interfaces were the only thing I was wishing for in the Clojure/JVM story. They will help me simplify code that interfaces with FoundationDB.
whalesalad 263 days ago [-]
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 was okay until someone wrote a thing in php (!!) in a day that would be have taken at least a month in Java. Maybe two. I understand the "good" of Java, but for me personally, the cost of that good is just incredible tedium. This is personal and I understand that.
You understand that this has everything to do with Java and nothing to do with the JVM, right?
vips7L 263 days ago [-]
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.
collyw 263 days ago [-]
PHP avoids many of the hassles of deployment that pretty much any other language does.
iLemming 263 days ago [-]
Slowly repeat after me... "Java and JVM are not the same thing." Do it ten times every morning.. jk. JVM always gets a bad rap because of Java. Matter of fact, JVM is an incredibly good piece of tech.
petersellers 263 days ago [-]
> I spent too many years of my life on Java. It was okay until someone wrote a thing in php (!!) in a day that would be have taken at least a month in Java. Maybe two.
Really curious to hear what that was, because I'm having a hard time believing that is true.
imzadi 262 days ago [-]
I love Clojure. My dream is to be a clojure developer, but there aren't a lot of jobs available and everyone wants 87 years experience.
mysore 263 days ago [-]
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.
prabhasp 263 days ago [-]
There’s also clojuredart to do a dart / flutter app. I’ve been working on one, and it’s been pretty pleasant to work on.
stefcoetzee 263 days ago [-]
The CLJS + RN route is doable. [0] Not familiar with the other approach.
You might also want a CSS framework. There are some options to write CSS in ClojureScript, but I prefer TailwindCSS which isn't a Clojure-specific thing and it works fine out-of-the-box with `.cljs` files.
You can swap out Reagent for Helix, which is a lower-level and faster wrapper for React. That said, Reagent does work with React 18 just fine and there's tons of docs for it, so jumping to Helix first is a premature optimization IMO, especially if you're new to Clojure.
parhamn 263 days ago [-]
> (+ 1 2 '(1 2))
> "3(1 2)"
Yup, I'm out.
ARandomerDude 263 days ago [-]
That's because this is actually ClojureScript (JS under the hood) running as a browser library.
If you try that in Clojure, you'll get:
> ClassCastException class clojure.lang.PersistentList cannot be cast to class java.lang.Number
If you try it in a "proper" ClojureScript dev environment (shadow-cljs), you get nil as a result with this warning:
> cljs.core/+, all arguments must be numbers, got [number cljs.core/IList] instead
parhamn 263 days ago [-]
Thats actually good to know, I thought this was reflective of Clojure itself.
yladiz 263 days ago [-]
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.
monsieurbanana 263 days ago [-]
Clojurescript compiles to Javascript and so inherits some of it's idiosyncrasies.
This is one of the real problems of clojure: you need some knowledge of the host language. It's also one of it's major strengths, and the only reason a lisp managed to get so much (relative) commercial traction.
gleenn 263 days ago [-]
That has more to do with Javascript being weird than Clojurescript. That would almost certainly throw a good exception when using Clojure / Java.
bunderbunder 263 days ago [-]
Fair. I don't mind dynamic typing so much, but I agree that getting clever with automatic type conversion is just plain wat.
I wouldnt call that getting clever, its just a string of what you did
> (type (+ 1 2 '(1 2)))
#object[String]
bunderbunder 263 days ago [-]
The problem is how you get there.
1 and 2 are integers. '(1 2) is a list. None of them are strings, but + helpfully converts them to strings because ClojureScript is leaking some of JavaScript's wat behavior.
TacticalCoder 263 days ago [-]
That's JavaScript for you. Here's a talk about the (in)sanity of JS:
The problem I see with clojure, isn't it missing autocompletes? You work with JVM/typescript libraries, but your editor isn't smart enough to pull types from those into clojure. That slows you down tremendously.
ilikehurdles 263 days ago [-]
No. When working with JVM/typescript libraries you have their autocomplete information.
If you need method autocomplete scoped to a type you can use the `..` macro for more concise call syntax:
(ns my-project.core
(:import (java.util ArrayList Collections)))
(defn example []
(let [list (ArrayList.)] ; ArrayList constructor can be autocompleted
(.. list
(add "Hello") ; The .add method on ArrayLists will be autocompleted
(add "World")
(add "Clojure"))
(Collections/sort list) ; The Collections.sort method will be autocompleted
list))
this other form will a also autocomplete everything after the dot (.) to call a method on an object:
(.toUpperCase some-str)
but the trick with this arrangement is to write that variable some-str first, then if you write . in front of it the autocompletes will be relevant to that object. But I gratuitously used threading macros like .. or -> to make it match java-style code (in other words: (-> some-str (.toUpperCase))).
createaccount99 262 days ago [-]
That's cool, I had no idea. In clojure itself I suppose you eval code and then get autocompeletes from the repl output? -- I mean for stuff with no types
simongray 262 days ago [-]
Autocomplete also works for the functions and vars in the namespaces. You don't really use methods in Clojure unless you're doing interop with Java or JS.
ziftface 262 days ago [-]
Autocomplete works for me with jvm imports
cospaia 262 days ago [-]
As a Clojure editor tool smith it pains me that this is the case. Especially for ClojureScript where I spend most of my time. I really want to fix this.
For JVM interop, I think Cursive (IntelliJ Clojure plugin) is smart enough to help with autocompleting Java libraries.
sswezey 262 days ago [-]
Yea, Cursive is the best by far for doing Java interop. It is really good.
FredrikMeyer 262 days ago [-]
I use Emacs with Cider+clojure-lsp, and the autocomplete/refactoring tools are super good.
android521 263 days ago [-]
Clojure will get more adoption if they find a way to make it very easy to build cross platform mobile and web apps like React & React native. They need to keep adding libraries to make the eco-system bigger.
> 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.
pron 263 days ago [-]
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.
>>The JVM cold-starts, loads a Hello, World program from a compressed JAR, runs it and shuts down in 40ms.
Im yet to reach the X-men level super qualities that can detect, and work in 40 ms chunks. Or at least even notice a 40 ms delays.
I envy the humans who can notice such small chunks of time.
pron 262 days ago [-]
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.
joshlemer 262 days ago [-]
I don't like how this issue is constantly dismissed out of hand. It very obviously is actually a massive glaring issue which severely limits what clojure can reasonably be used for. Nobody would ever accept a 1 second startup time for CLI applications that we use all the time like git, kubectl, npm, docker, etc.
262 days ago [-]
kamaal 262 days ago [-]
npm, docker, kubectl honestly, you are splitting hairs. I plan to run a process for hours, I think 40 ms delays are something I can live with.
git- I take more time to write the commit message to worry about 40 ms.
I mean sure optimise code to run it fast. But its not something that a human notices.
joshlemer 261 days ago [-]
40ms? Clojure Hello World startup time is like 600ms and up. Yes sometimes you run npm and kubectl for hours but most of my usage is just running individual commands that take less than a second.
didibus 263 days ago [-]
You still don't see many scripts or CLI apps in Java though. I was wondering if that would change now that you can run a source file directly, but then wouldn't the startup be slower, because now it's also having to compile.
geokon 263 days ago [-]
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 ?
pron 262 days ago [-]
That's pretty much the idea behind Project Leyden's "premain" work. The tricky bit is that the program startup being almost exactly the same each time isn't quite the same as being exactly the same. The JVM already caches some things and the capabilities of that mechanism are being expanded to cover more, including JITted code as well as some program computations done at initialisation.
260 days ago [-]
fiddlerwoaroof 263 days ago [-]
Back when I wrote Clojure professionally, using GraalVM to generate a native executable of things like clj-kondo basically eliminated the startup latency.
pron 262 days ago [-]
Right, but Native Image comes with its own tradeoffs. Lyeden's "premain" work aims to be somewhere between the situation today and Native Image.
rads 263 days ago [-]
It's a tradeoff. The startup time of Clojure on the JVM is slower than most, but the runtime is faster than most. It also needs a lot of memory to get going. This means it's optimized toward long-running programs like web servers. During development, you use the REPL interactively which makes this a non-issue, but it does take some getting used to at first.
That said, there are alternative runtimes that have different tradeoffs. For example, Babashka is a runtime for Clojure that uses GraalVM instead of the JVM as the foundation. Babashka scripts have about a 10ms startup time on my M1 MacBook Air.
rtpg 263 days ago [-]
My understanding is that this isn’t really a JVM thing, but I might be wrong.
koito17 263 days ago [-]
Your understanding is correct. JVM startup accounts for a small portion of Clojure's startup time.[1] Most of the overhead is in compiling and loading clojure.core. Efforts have been made to remedy this issue[2] (e.g. direct linking, ahead-of-time compilation, ...), but this doesn't remove the fact that clojure.core is huge and virtually any Clojure program will be importing more than just clojure.core, so there is still a lot of var derefs to deal with, then code to compile, then classes to load, etc.
Well, it's still partially the JVM at play. For example, if your application has big classes, and many classes, the JVM will be slow to start. This is what is happening here. Clojure is like a large-ish Java project, it has big classes, and many of them, with static initializers, that need loading at the start, and the JVM does all that slowly.
In some sense it's Clojure's fault for having an implementation that causes slow JVM startup, but it's also the JVM's fault that the way Clojure uses it causes it to take a long time to start.
pjmlp 262 days ago [-]
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.
didibus 262 days ago [-]
Can you talk more about these?
pjmlp 262 days ago [-]
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.
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).
Well, quite a few people already use AOT with PGO from GraalVM to build native executables of Clojure programs. Those start stupidly fast. I never heard of anyone doing so with OpenJ9, how good is the AOT of OpenJ9?
AppCDs in OpenJDK currently has terrible ergonomics. Clojure can't really offer it. Each user must go out of their way to leverage it. So you can't really release an app that automatically leverage it, the user needs to launch it with all the command incantations, etc. And it's so sensitive to class path changes, etc. It kind of sucks to be honest. But some people still use it for prod release, since you can set it up in a docker easily. But the use-case for fast startup are desktop apps, CLIs, scripts, etc. And for all those, AppCDs are super annoying to setup. See: https://ask.clojure.org/index.php/8353/can-we-use-appcds-to-...
Still, AppCDs don't fully solve the startup issue, because all the static initializations stuff takes a considerable amount of time, and that does not get cached by AppCDs.
neilyio 263 days ago [-]
When you get into REPL-driven development, the JVM startup time (which is often under a second for me anyways) is a total non-issue. You don't continuously restart your program to see changes or run tests. You can refresh all your state instantly without exiting.
But before Babashka, that was indeed a barrier to using Clojure in shell scripts. Now we have it all!
geokon 263 days ago [-]
Except in Babashka you can't use Java libraries.. Right?
I feel a good fraction of code will dip into Java libs at least a bit - so you're limited in what libraries you can use
I think the real solution is probably Graal native - though it's not part of the official toolbox/deps.edn
tanelso217 261 days ago [-]
[dead]
amelius 263 days ago [-]
In Unix it is customary to invoke tools from the Bash shell or Bash scripts. That doesn't mix well with a separate REPL.
neilyio 263 days ago [-]
I see your point here... I was addressing to the feedback loop during development time. Babashka works well for Clojure in a Unix tool pipeline.
It's also possible to compile your JVM Clojure program yourself to a binary with GraalVM for even better performance than Babashka and even faster startup.
fiddlerwoaroof 263 days ago [-]
Clojure and Common Lisp are the two cases where I’ve never felt the need to work in bash: most projects grow a library of utilities for development that aren’t limited by the stringly-typed nature of bash or zsh
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.)
pjmlp 262 days ago [-]
They are working with PGO, and adding also AI based optimization algorithms, both can help.
pjmlp 262 days ago [-]
The problem lies with Clojure implementation, not Java or the JVM.
paulddraper 263 days ago [-]
It's not the JVM.
It's how much code you load.
didibus 263 days ago [-]
I mean... That's not an issue in other runtimes, so it's kind of a JVM quirk no?
pjmlp 262 days ago [-]
Those other runtimes also don't do half of the features a JVM usually has to offer, and most people complaining also don't bother to actually learn the Java ecosystem, the existing set of JVM implementations, and the optimizations features made available to them.
paulddraper 262 days ago [-]
No, that's defiantly true in other non-native runtimes.
E.g. any Python project has to deal with this, like Mercurial.
Java projects tend to (insert stereotype) have a lot of code.
263 days ago [-]
pants2 263 days ago [-]
Cool site, I've never tried Clojure before. My first reaction though is that (+ 1 1) is a highly unconventional and possibly confusing way of writing 1+1, and I'm not sure why that design choice came about.
widdershins 263 days ago [-]
Using the same notation for function calls and math operators has many benefits that may not be immediately obvious, and which may (depending on preference) outweigh the unfamiliarity. For example:
No operator precedence rules to remember.
Use math operators in higher order functions:
(reduce [1 2 3] +)
(compose + \*)
Simpler parser in the language.
Use kebab-case variable names.
Use most symbols in variable names.
The last two are because of reduced ambiguity in the language.
The benefits for readability can be huge.
For example, I love question marks for predicates in Scheme.
(vector? x)
(filter [1 2 3 4 5] odd?)
I'm sure there are more benefits that I haven't thought of.
ReleaseCandidat 263 days ago [-]
> Use math operators in higher order functions:
To be fair, there just needs to be syntax for passing infix functions as a "normal" function, like parens in Haskell
(reduce [1 2 3] +)
foldl' (+) 0 [1, 2, 3]
And to allow `?` in names, it "just" shall not be used elsewhere.
netbioserror 263 days ago [-]
In prefix notation (which has been around for a very long time) the operation comes first. Think of it as the "addition" function being applied to the arguments following it. This matches how nearly all programming languages are broken down into their abstract syntax trees while compiling.
As an added bonus, you now know Lisp. This is how all Lisp syntax works: `(function arg1 arg2 ...)`. That's literally it.
resource_waste 263 days ago [-]
[flagged]
dannyobrien 263 days ago [-]
"Typical convention" is also an Appeal to Tradition. Different traditions though!
Incidentally, people used to be /really/ against Python because of its use of significant whitespace. The number of people who bounced off it because they would try to type something and then get a mysterious syntax error that turned out to be that they didn't type space or tab the right amount of times! It used to be what people would immediately post whenever anyone said anything about Python.
I think now people get introduced to IDEs at the same time as Python, or something happens at that early teaching moment that gets them over that hump. The same, in theory at least, happens when you play with a lisp long enough.
bigstrat2003 263 days ago [-]
I still, to this day, think that significant whitespace in Python is an awful design choice. I avoid it for that reason.
amanaplanacanal 263 days ago [-]
Calling it unreadable is a stretch. It’s just using the same syntax for all functions, instead of having something special for arithmetic.
ertgbnm 263 days ago [-]
Prefix notation isn't unconventional and it has many benefits.
The main one being if you want to add many many items together it's much easier IE (+ 1 2 3 4 5 6).
Instead of doing an operation between two numbers you are apply a function to a list of inputs. In the "+" function's case you are adding them together. With this in mind, it's no different than most other programming languages. You would never have your function be in the middle of your arguments/inputs.
In python its Foo(bar, bar) and not bar Foo() bar, which obviously doesn't make sense.
julianeon 263 days ago [-]
On the contrary - I think it's much easier!
The real advantage of this notation is that it's much, much easier to stack calculations.
Example:
(/ (+ 34 68 12 9 20) 140)
You can imagine how the first part of that could come about:
(+ 34 68 12 9 20)
And then the second part (pseudocode):
(/ sum 140)
In Clojure it's easy to mash them together, or for example to paste the first calculation in the second.
(/ (+ 34 68 12 9 20) 140)
Want to go further? Easy: add another enclosing parens.
(* (/ (+ 34 68 12 9 20) 140) 1.5 2)
Note how we're stacking in a more human-readable order - a new calculation starts on the left hand side, the first thing we see as we read it.
Compare how verbose the alternative is:
((34 + 68 + 12 + 9 + 20) / 140) * 1.5 * 2
eviks 263 days ago [-]
I'd take the verbosity over having to count the operators from the start of the line to figure out which one are used at the end of the line "1.5 2"
skydhash 263 days ago [-]
The parenthesis highlight in any decent editor. So it always very obvious which operators apply to the operands.
eviks 263 days ago [-]
This reduces the friction, but doesn't eliminate the needles back and forth. Breaking context locality is just bad
The threads example in another comment is way better
skydhash 262 days ago [-]
There’s no needle back and forth. Everything in parentheses is a complete expression, like a formula. You have the operator (function) and the operands (args). Think of it as add instead of + and multiply instead of *. You get used to it very quicly and then it does not matter.
eviks 261 days ago [-]
It's not about thinking differently, it's about operator location, you simply don't know with long nested function what operation is performed at end-3 nest unless you look at beg+3
ReleaseCandidat 263 days ago [-]
> (* (/ (+ 34 68 12 9 20) 140) 1.5 2)
The problem with that is by the time I reach `20)` I have already forgotten what operation I'm in. I'd write it more like this:
(*
(/ (+ 34 68 12 9 20)
140)
1.5 2)
actually I'd write this as
(/
(* 2 (/ 3 2) (+ 34 68 12 9 20))
140)
> ((34 + 68 + 12 + 9 + 20) / 140) * 1.5 * 2
Why not (34 + 68 + 12 + 9 + 20) * 1.5 * 2 / 140?
floodle 263 days ago [-]
A programming language should be first and foremost precise and readable, not concise. I can barely understand the clojure version but when I read the "traditional" one I don't even need to think.
If you work with clojure a lot, does it become natural?
barrell 263 days ago [-]
I've worked with clojure for years, and it becomes pretty normal, but I don't think it inherently get's better than the alternative, just on par... although it's realllly nice not to ever have to think about operator precedence, especially if you have to switch between languages.
Where it really shines though is when you use the threading macro and inline comments to make really complex math a breeze to review:
(-> (+ 34 68 12 9 20) ;; sum all the numbers
(/ 140) ;; divide by a quotient for some reason
(* 2) ;; multiply by two for it's own reason
ARandomerDude 263 days ago [-]
Yes, especially because the language is so consistent.
99% of the syntax is just (foo arg1 arg2).
julianeon 263 days ago [-]
I would say yes, as someone who got used to it a few years ago.
ARandomerDude 263 days ago [-]
Use the thread macro to make this easier to think about:
(-> (+ 34 68 12 9 20) (/ 140))
kazinator 263 days ago [-]
It resembles command language: + is the command, and 1 and 1 are arguments.
mv foo.txt bar.txt # rename a file in the Unix shell
Except there are parentheses to delimit the command because commands can be nested in each other.
(Some Lisps have interactive modes where you can drop the outermost parentheses, allowing you to type like this:
prompt> + 1 1
2
but usually the parentheses are part of the formal syntax; where this is just a hack in the interactive listener.)
The design choice came about because the syntax started as an internal representation for a symbolic processing (formula manipulation) system that was being designed by John MacCarthy. The internal representation where the operator is first followed by its arguments was convenient because you don't have to parse around to identify them. You know immediately that by accessing the first position of the formula, there is going to be a symbol there which identifies its operator.
The internal form, and its written notation came to be used directly, while the symbol manipulation system came to be programmed in itself, so that its own "formulas" (source code) ended up in that form.
sek 263 days ago [-]
Lisp is one of the oldest programming languages developed in the 1960, it came before and is very elegant actually. You can write incredibly powerful things with this pattern and it's also really simple to build an interpreter for it with the stuff they had at their disposal at the time.
baq 263 days ago [-]
See also: 'Recursive Functions of Symbolic Expressions and Their Computation by Machine' (1960) [1]
When McCarthy was first working on Lisp he intended to migrate to an infix syntax (called m-expressions), but abandoned the effort because everyone came to really like the current syntax (s-expressions) after they got used to it.
I know it's really hard to see how. (And honestly it's sometimes a little hard for me to see what the big deal is, but one of my first programming languages was a dialect of lisp so I can barely even remember not being comfortable with them.) But many people find that, once they get used to them, and especially if they learn to use an editor with a paredit mode, they can start to feel even easier to work with than infix syntax.
+ is a function, there are no operators in Clojure.
That allows for some nifty tricks like:
(reduce + [1 2 3])
; returns 6
globular-toast 263 days ago [-]
Another neat thing is that these functions are n-ary, and that includes 0 args. So (+) => 0 (additive identity) and (*) => 1 (multiplicative identity). Little things like this are missing from languages like Python and make functional programming worse than it should be.
tasuki 263 days ago [-]
Oh wow, this is truly great! Would you have a link to the code behind `(+)` and `(*)` ?
(In my functional programming languages, `reduce` also requires passing an initial value, like `foldl (+) 0 [1, 2, 3]`. But surely it should get a monoid instead of two separate arguments for the operation and the starting value!)
Yeah, it always annoys me to have to pass an initial argument. The idea of identities is built into Lisps and seems conspicuously missing from other languages and I don't know why. In Python and JS people have to write stuff like `lambda x: x` over and over again. The identity function is surely special enough to get its own name.
tasuki 261 days ago [-]
This does not involve an identity function?
It's just a textbook monoid: an operation on two elements which produces another (+ or *), with a "neutral" element provided (0 or 1).
globular-toast 260 days ago [-]
I know this isn't the identity function, but it's the same idea and a feeling I get when using Lisps that the language is kind of "complete". Including the identity function and having built in 0-ary functions where it makes sense are examples of this.
globular-toast 263 days ago [-]
Your comment is a highly unconventional and possibly confusing way of writing... to a Chinese person ;)
kamma4434 263 days ago [-]
Because (+ 1 2 3) makes 6. This said, doing any kind of maths in clj, especially division and comparisons, is a PITA - I’d go as far as to say that’s the worst part of the language. Otoh, (< 3 x 4 y) is pretty cool.
rgrmrts 263 days ago [-]
That’s prefix notation [0] and not unique to clojure.
Try 1 + 2 + 3 + 4 + 5 versus (+ 1 2 3 4 5) for one nice reason.
263 days ago [-]
taneem 263 days ago [-]
The way to think about it is that you're processing a list, where the first argument is an operation, and the subsequent arguments are numbers to add. Clojure, and other Lisp based languages use this structure (a list) for representing everything - both data and functions. It turns out to be a very simple yet endlessly flexible and extensible concept.
weatherlight 263 days ago [-]
because lisp and consistency
(some-function-name arg1 arg2)
idontpost 263 days ago [-]
[dead]
throwaway726366 263 days ago [-]
[dead]
263 days ago [-]
TZubiri 263 days ago [-]
[flagged]
whalesalad 263 days ago [-]
Type your Clojure symbolic expressions here
=>(+ 1 2)
3
=>(* 1 2)
2
=>(* 2 2)
4
=>(/ 100 10)
10
=>
twism 263 days ago [-]
=>(def ÷ /) =>(def × *)?
mise_en_place 263 days ago [-]
What a bizarre critique of LISP, which allows you to arbitrary define symbols to mean literally anything.
TZubiri 260 days ago [-]
That's the laziest excuse ever, I want good defaults, and maintainers somewhat receptive to criticism.
"Technically you can configure our product to do what you want" is a great way to have shitty defaults and to ignore all criticism you don't want to heed.
Also this is not a lisp critique, it's a clojure criticism. I get that lisp is simple, ancient and set in stone. Clojure or whatever layer on top of it should be defining this basic stuff on top of lisp.
Are we expected to build a 3rd layer of lisp on top of lisp? And then what pass the burden to our users and let them define a 4th layer of what they actually want?
We need opinionated developers who choose a set of values or industry or application, not indecisive devs who want to support every user.
kazinator 259 days ago [-]
What is your example of a bad default in Clojure, and what should it actually be?
Is it the stuff in your grandparent comment? That there should be some binding for x out of the box so that (x 1 2) does something rather than error out?
A lot of mainstream languages use * for multiplication, so that would just be inconsistent.
I also don't agree with your strange requirements where you propose that the character ÷ (U+00F7 division symbol) be used for division, whereas for multiplication you are proposing the plain lower case x letter from ASCII (U+0078).
What you want to pair with ÷ is the actual multiplication symbol × (U+00D7).
Separately from that inconsistency, it's probably a bad idea to introduce one-letter like x into the standard library of a language, particularly one that is Lisp-1 (one namespace for functions and variables).
Those math symbols ÷ and × are both hard to type (does not appear on keyboards and not supported nicely by common input methods) and easily confused for x.
> We need opinionated developers
Okay: Programming languages should stick to ASCII, get off my lawn.
Supporting Unicode in identifiers is fine: give that to the application programmers to use in their programs, if they are so inclined, but for Pete's sake, keep it out of the language and libraries.
(Some Lisp authors disagree; for instance, there is support for lambda being written using the actual Greek symbol in some Lisps.)
DerCommodore 263 days ago [-]
[flagged]
dgoraa 263 days ago [-]
why do you need to disclose using that reader comment history has that appended to most of your comments, its rather annoying
xyzzy4747 263 days ago [-]
I'm happy with TypeScript, Go, and Rust. Don't feel like learning anything else.
BeetleB 263 days ago [-]
Do you post this comment on every thread involving a language that isn't once of your Holy Three?
Personally I don't feel this strong urge to tell communities that I'm not interested in them. Waste of my time and theirs.
ekzy 263 days ago [-]
Then… don’t? No ones is forcing you to try it or even to comment with your close-minded opinion. What does this comment bring to the conversation?
iLemming 263 days ago [-]
> Don't feel like learning anything else.
If you need to do graphql in typescript, you still "learning a new language". If you're using Prisma or you doing css-in-js, or rxjs, or tRPC, or Cypress, you're still "learning a new language".
You're basically moving from having to understand one (seemingly arbitrarily composed) syntax to another (seemingly arbitrarily composed) syntax. While I learned Clojure syntax once, and now only have to learn paradigms and techniques - logic, pattern-matching, concurrency, reactive dataflow, polymorphic dispatch, metaprogramming, event-driven programming, state management, etc. etc.
My hope is that I never have to use TypeScript, Go, or Rust daily since Clojure already gives me everything I need to write programs. It simply makes much better sense for the type of programs I'm writing today. Someday that may change, but I doubt that any of those would suddenly start making better sense to use. It probably will be something else, not these.
jwr 263 days ago [-]
I'm happy with Clojure, having progressively discovered better and better programming languages over the years, and yet I'm constantly on the lookout for new languages and approaches.
Don't feel like being the Blub programmer and missing out on new good stuff.
latexr 263 days ago [-]
Weirdly appropriate that the first thing on the website is this quote:
> If you want everything to be familiar, you'll never learn anything new. - Rich Hickey
zug_zug 263 days ago [-]
I'm with you. I'll even go a step further, since I haven't heard anybody else say this:
My hot take -- There are already too many programming languages in use and every engineers life would be easier and productivity would be higher if we standardized.
There is almost no situation where making an entirely new language is the optimal solution. And the worst reason of all to make a language is anything aesthetic (e.g. spacing, parentheses, notations) -- all of those cases should be handled via native transpiling in the IDE.
rads 263 days ago [-]
Hmm, maybe we could come up with a standard syntax? We could call it “Standard-expressions”, or perhaps “S-expressions”? That way an entire family of languages could use the same syntax. Now how do we get people to follow this standard?
iLemming 263 days ago [-]
If we were forced to use only selected, most popular languages to communicate and ban all others, everyone would have to speak Mandarin, Spanish, and English. And the world would never know the Quran, the Bible, Arabian Nights, the Epic of Gilgamesh, War and Peace, the Ramayana, or the Divine Comedy.
There are some good examples of applications that are specifically written in Clojure and would be quite difficult to replicate in other PLs - Roam Research and Logseq, XTDB and Datomic, Nextjournal and Precursor.
> And the worst reason of all to make a language is anything aesthetic
Interestingly, people avoid learning Lisp, because it doesn't look "sexy enough" the first time they see it.
I'm not sure if you meant this as evidence for or against my point, but this is exactly what I mean.
Every year dozens of people think they're going to make some holy-grail language and it's 1% different from the prior version but now every single library needs to be rewritten, a dozen years of patches/bugs/security-holes must happen, whole resumes get shined up rewriting tech stacks that were perfectly capable, all for it to be thrown for the next shiny toy in less than a decade.
A new language just fractures knowledge.
cess11 263 days ago [-]
Is that how you see people that know several natural languages? You think they're intellectually fractured?
zug_zug 262 days ago [-]
I don't really get what you're getting at here.
It sounds like you're trying to draw some equivalence between multiple spoken languages (largely seen as something brag-worthy-ish) and multiple programming languages. Like if culture thinks it's sexy to know french that somehow that means it's sexy to know Cobol [It's not].
The two are wildly different of course, a programming language can be learned in weeks and a spoken language takes years.
But if your question is -- would the world be a better place if we all spoke the same language? Then yes, absolutely.
Fortunately AI will help continue to reduce these artificial barriers between languages.
cess11 262 days ago [-]
I'd consider a person that has only studied programming for weeks extremely junior.
Edit: And no, "AI" is not going to affect any such barriers. It's already trivial to learn new natural languages, and some people do, some don't. Those who don't, generally don't care about people that speak other languages than the one they learned in childhood.
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.
The creator, Anthony Grimes (https://github.com/Raynes), sadly passed away seven years ago: https://www.reddit.com/r/Clojure/comments/5gyyxw/clojure_ope...
To me, switching to a less expressive language is painful and infuriating. I remember having to switch from Python to Java 5, and how everything started to take 3 to 5 times longer code to express.
Maybe the key thing is to only write small things in less expressive languages, like shell scripts, or small C functions to help performance or FFI, or, well, tiny Go utilities.
There's a population of people that don't like this kind of abstraction, because to understand the code you first need to understand the problem, the data flow and how it's expressed. They prefer to look at the issue "bottom up" than "top down", and for them Go is perfect, because instead of reasoning about what the module does, how the data flows, what the algorithm does, they can focus on the small: this is a loop that iterates through a slice, this is an if condition that depends on this variable.
There's no better or worse here, just two types of people. Some like to look at the whole bridge, some like to examine the individual nuts and bolts and joints.
No, two types of solving a problem. I like writing both (Haskell and Go) - for _myself_ - but the tooling and standard library and ... of Go is orders of magnitudes better.
During my time there, I was genuinely impressed by the quality of many of the internal tools. E.g. it took GitHub code review tools a long time to get on par with Google's internal code review tools from 2015.
"Quite good" is exactly the wording that I would have used too. But Go's tooling is _really_ good; the language itself is, well, "quite good". And is still supported and actively developed!
All of these are perfectly mediocre and usable languages (in which I would write in if somebody pays me), but surprisingly even for myself I ended up really liking Go.
Then a week later, the squinty mole discovers the code is dead; not called at all. There is no slice and no variable.
How does this affect readability when one tries to dive into an existing Go project? Is Go code harder to navigate than code in more expressive languages?
With a simple problem, and a small codebase, Go is easier, because you need to know less language constructs.
With a complex problem, and a large codebase, a more expressive language is easier if used properly, because it can highlight what's important in the solution. The ratio of signal to noise when reading code is higher.
You can get incredible mileage out of Go using just the stdlib, built in tools, and the pure go port of SQLite. With Clojure it feels like I have to stitch a million random libraries together to get anywhere. Writing Clojure is delightful but the ecosystem and tooling pale in comparison, and at the end of the day I think that’s what matters.
If Clojure had great built-in tooling, better error messages (and maybe some SBCL-esque type checking features), and if Datomic was open source and less rough around the edges, I think the language would have been far more successful.
Making historical interfaces well-typed is another kettle of fish. For instance, Python's range() is actually two overrides with incompatible signatures. I bet PyTorch has more of such examples.
an expressive language (clojure, python, C#, etc.) is good when you're iterating "what problem am i even trying to solve?"
a less expressive language is typically better when you more or less know a high level solution to your business problem, and you're iterating on "how do i make the machine actually do it". like "how do i reduce my AWS bill" or "how do i render it in 16ms?"
for something like gamedev, enough of the problem is in the second category that you might not even reach for the expressive language. (many do, though. with lua, or artisanal lisp dialects)
To me it isn't more infuriating than learning a new language or practicing one I'm not very good at. There's a bit of friction, but that's common when I'm developing in the languages I'm most fluent in too. Either that or the problem is trivial and likely to be in most common programming languages.
If you are making JavaScript look good you are failing as a language.
I think its OK to be reminded that the context is mainly immutable when chaining on a stream.
Method polymorphism to achieve default parameter values is a much worse nuisance, but not particularly hard to get used to when one decides to focus on the problem rather than the inevitable warts of a broadly useful programming language.
Edit: Also a pretty weird complaint in a thread about a programming language where pretty much all the basic data structures are immutable. Maybe take it to the top and make the quip about worse than JavaScript with regards to Clojure?
Here's JS showing the same issue.
The correct fix is that each loop iteration gets a fresh i instead of i just being mutated. Currently you can't see the difference in Java between there-is-only-one-i and there-is-a-fresh-i-every-time, so Java is free to do this right.Edit: Oh, right, it also solves the 10-issue. Thanks, didn't realise at first.
(Say what you want about Oracle Corporation, but as the wardens of Java they're doing a pretty good job.)
Might still end up marrying only one though!
C++ or Rust are expressive in both directions, towards the metal and upwards in terms of abstractions. But those languages are not easy to reason about. They come with lots of stuff, hard to grok abstractions, ceremony and so on.
Small languages like Scheme or Lua are expressive in that they don't force you to write a lot of stuff to get the job done. But those languages don't typically let you express what the computer should actually do at a lower level.
C or Zigs let you do those things and are also relatively small languages. But their facilities for abstraction are limited and they force you to express more computational details.
Go frees memory and schedules goroutines for you, but lets you express a lot more about how you lay out memory than other managed languages, so you can actually think in terms of bits and bytes. It has a great std lib so you can get stuff going quickly and reliably.
But sometimes limitations can spur creativity.
I do not see a significant difference in the amount of boilerplate needed for catching exceptions, pattern matching or branching on the existence of an error value. Nobody can stop you from implementing `bind` in Go for your error (which I did in some places), but of course Rust "wins" with its `?`.
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
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
There is no "commit" / "deploy".
https://biffweb.com/p/xtdb-compared-to-other-databases/
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:
shadow-cljs[2] makes using npm libraries easy.
i’ve settled on go backends and reagent frontends as my default setup[3].
1. https://reagent-project.github.io/
2. https://github.com/thheller/shadow-cljs
3. https://github.com/nathants/aws-gocljs
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...
i don’t even know what the difference between hooks and classes are, reagent has never given me a reason to care.
if that’s not simplicity, i don’t know what is.
if i were to build a 3d high performance game in react, maybe i’d experiment with dropping reagent. instead i’d just use cpp.
not sure why people want yelp complexity websites to be 3d games.
i love react! i absolutely do not care about the details. never have…
this setup is a single go binary that serves a single pre-gzipped html file from a lambda.
that html file contains inlined js, that is a reagent app.
there is also a favicon. total of 3 files in the lambda zip.
you desired setup is certainly possible though. you’d probably want clojure on the backend though, so you could more easily do SSG.
[0]: https://www.braveclojure.com/clojure-for-the-brave-and-true
For me, the biggest benefit is that it's hosted. Learning only Clojure, I can easily today write for JVM, .NET, JavaScript, Flutter, or shell scripts. Even when I need to write Lua, I'd usually pick Fennel. It's not Clojure but feels very similar. There are libs that can give you Python or R interop from Clojure. There are projects to target Golang, Rust or Erlang. Jank is a super interesting, experimental implementation of Clojure that runs on LLVM, I'm very excited about it. Do you want to become a true polyglot programmer? You only need to learn Clojure.
Isn't CL better for this? You are not bound to some VM and you can produce native binaries.
Clojure is interesting, but some Clojure APIs (stuff like Spectre) is "I want this everywhere now" stuff.
Furthermore, the data structures in clojure also have interfaces that make it easier to swap out which data structure you're using while still keeping whatever map/filter/reduce algorithm implementation you're sending it through.
Common lisp, on the other hand, has setf. Which more or lets you mutate anything. You certainly can code in an FP style in common lisp, but it doesn't restrict you in any meaningful way. Not a problem if you control the entire codebase, but when gluing components together this can be a source of friction.
But in Clojure, FP is kind of mandatory, there's no OO for example, and it's more than just first-class functions, it's also immutability.
But: you can also build regular Clojure programs using GraalVM and create a fast-starting binary.
jank uses immer for that: https://github.com/arximboldi/immer
Try Clojure – An interactive tutorial in the browser - https://news.ycombinator.com/item?id=30423856 - Feb 2022 (93 comments)
Try Clojure in your browser - https://news.ycombinator.com/item?id=3366526 - Dec 2011 (26 comments)
Try Clojure - https://news.ycombinator.com/item?id=1359682 - May 2010 (60 comments)
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.
Instead, this fact always seems glossed over, and because of it anyone who spends 60 seconds writing lisp by hand into a repl assumes that's the lisp experience and nopes out forever. When in fact paredit makes lisps the easiest-to-edit languages in the world.
That is true and whenever I switch from Clojure to C, I immediately feel the pain (what do you mean I can't just kill or lift this entire expression?).
But structured editing has a learning curve, so it's a difficult balancing game. Beginners are overwhelmed by all the parentheses, while experienced programmers don't even notice them and love the structured editing approach.
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.
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.
It's surprisingly excellent! Sure, the "language" of paredit features more powerful text manipulation that just simple movement... but combined with the new "jumping" in the latest Helix release [1], it makes for a very impressive keyboard-based navigation system.
[0]: https://helix-editor.com [1]: https://helix-editor.com/news/release-24-03-highlights/
(my-name "<img src='#' onerror=alert(1) />")
[0] https://codeinterview.io/languages/clojure
map, filter and reduce are extraordinarily powerful, especially when combined with the other core library functions.
[0] https://github.com/redplanetlabs/specter
Is functional programming too weird for majority of computer programmers? Is functional programming not optimal for solving industry problems? Maybe there is no functional programming that that is up to some set of standards? Is functional programming too complicated?
I do enjoy functional programming myself, and while I don't use a functional programming language at work, I try to use functional programming paradigms when I finds it suits the probem.
Clojure is successful, stable and well maintained. However it is a niche language compared the big mainstream languages. It takes a functional approach by default, but doesn't shy away from other paradigms/patterns/idioms when appropriate.
Functional programming itself has had a huge influence on mainstream languages since about a decade and a half. More and more pragmatic features have been introduced in mainstream languages. Newer languages have adopted functional idioms from the get go. On the macro level as well, ops and architecture have been adopting statelessness, reproducibility and so on.
The consensus has shifted towards containing state. The more moving parts you have, the harder it gets to reason about a whole thing. Treating data as data.
OOP has also changed and got refined over this time. I think people realized that it has good ideas (generic interfaces, polymorphism etc.) and bad ideas (inheritance, local state etc.). Same thing with FP, there's stuff that's esoteric and too far from the reality of actual programming and stuff that is pragmatic and simplifying in there.
Higher order functions, immutable data structures, declarative UI frameworks, etc are things you're quite likely to encounter in a contemporary codebase these days.
Python’s had the GIL while Haskell has had a well functioning parallelized runtime for a long time. And both Haskell and OCaml’s runtimes outperform Python’s and are on par with Node.[1]
As for documentation, every time I come back to Hoogle,[2] I remember how I miss it in other languages that just don’t have anything similar going. The lack of types doesn’t help, and reading the documentation of the Python standard library or a popular library like torch is sometimes like solving a puzzle because it’s unclear what a function or a class constructor really does or even what class’ instance it returns.
[1] https://benchmarksgame-team.pages.debian.net/benchmarksgame/... for CPU usage but their memory usage is comparable too, and OCaml’s benefits from the language’s eagerness.
[2] https://hoogle.haskell.org/
Also, but this is entirely on me being an absolute dullard, I like having the brackets around each binding as a kind of guardrails for my mind to not lose track of where I am and what belongs where.
=> (+ 2 2 + 3)
"4function Cg(a){switch(arguments.length){case 0:return Cg.s();case 1:return Cg.g(arguments[0]);case 2:return Cg.h(arguments[0],arguments[1]);default:for(var c=[],d=arguments.length,e=0;;)if(e<d)c.push(arguments[e]),e+=1;else break;return Cg.j(arguments[0],arguments[1],new gc(c.slice(2),0,null))}}3"
Babashka cli returns this, telling you clearly what the error is:
(if (= 2 2) :OK :nope)
On the other side I've felt a lot of the ecosystem work that was done has a more "timeless" quality. Coupled with java interop I haven't ever felt wanting when I do reach for it for some hobby projects I keep up with.
One thing I will say, since it takes a different tack and philosophy, I think any programmer learning some of it benefits from the different perspective.
There was an almost apologist attitude towards rough edges or failure modes folks new to the community would fall into. I managed to work with it for a time despite that, but it left me with a bad taste in my mouth. It's really important to make your ecosystem and communities welcome to new users or they're destined to fade.
If anything was "hostile" it was the dominance of emacs and very limited options for other tooling. That, and the horrifically unhelpful leaky abstractions in the stack trace when something went wrong. It's a big step, if you don't already know emacs to learn emacs (at a fairly advanced level) AND a new very different programming language.
I see now there's a vscode option with Calva. I haven't tried it. Might do that someday. Really wanted to like this language!
The official Clojure CLI, for example, is just plain confusing, and that's most people's first impression to the entire ecosystem. The config files, while they use the wonderful `.edn` format, are also not intuitive. Newcomers are the most likely people to encounter the most amount of error messages... which are famously difficult to read.
And that's before you even get into REPL configuration, which involves coordination with your editor process, a client process, and a server process. Even if you have a tool like Calva or CIDER managing it for you, you'll still get confused when something goes wrong unless you grok all the moving parts.
Even if you figure that all out, you still don't have Parinfer or equivalents setup yet in your editor. Also, clojure-lsp tends to require some configuration to get working the way you want. And that's before you get started with ClojureScript, which brings the complexity to another level entirely.
Despite all this, I love Clojure. It's an expert's language, even though it shouldn't have to be. Once you learn this stuff, you respect why much of this complexity exists. It's inherent to the amount of power the language gives you.
But doesn't mean we can't make it easier to use and get started with.
I completely agree; it is unfortunate they don't spend more time officially recommending leiningen.org. A beginner attempting to use the built-in CLI is going to lead to a poor first 3 months.
There is one cultural thing that might be confused with unfriendliness. Sometimes people react badly if someone posts incorrect information. But I think that's good. When you search for information about Python or PHP you have to wade through quite a bit of junk. Ironically, it's sometimes easier to find correct answers for Clojure.
Clojure itself is very clean and consistent, it's got a lot of polish to it, which makes it comparatively easy to learn. And there isn't that much of it. But for a long time the tooling was hard.
That's far less of a problem than it used to be. deps.edn and shadow-cljs both made things easier, as has Cursive. People say nice things about Calva, but I don't know it.
I'm a big fan. Babashka alone is enough to make learning Clojure worthwhile. Also, for someone like me, it's kind of nice that it feels almost finished. Once you learn it, you know it, and now that the tooling has settled down a bit you don't have to keep running to keep up.
I don’t think the solution is the Usenet-esque “you are wrong and your breeding is suspect” way. But there’s a very good place in the middle and I’d really like to find that place.
Business wise, I think we’re using the wrong paradigm in some major places. Maybe we can beat that while I’m still alive and that would be a net win for our whole craft.
i tried calva with vscode afterwards but it was basically the same thing, calva has paredit which overrides the keyboard navigation and there was no way to disable it. somebody had asked for the option to disable it on the github but the creator said something along the lines of calva without paredit wasn't the way he wanted it to be used, so i just picked another language to learn.
By default Calva Paredit overrides three or so keyboard shortcuts. This is to help the users to not accidentally delete brackets, which in turn keeps the support questions down about broken Clojure code. I have tried to make it super easy to disable these overrides. There's a command for it. There's a button in the toolbar for it, and there's a setting for it.
There's also a setting for removing all Paredit bindings, even those that are not overriding any default ones. (This setting is for people who use Paredit, but want to provide their own shortcuts entirely.)
if there's now a way to disable paredit entirely then i might check it out again some time in the bear future
To this day, I will still have weird issues where Cursive won't run a project, and Emacs will.
All that being said, I do really like Cursive.
https://github.com/clojure-lsp/clojure-lsp
https://github.com/clj-kondo/clj-kondo
I've always used (Neo)Vim with Clojure.
Some of this comes from lisp's minimalist syntax, which makes it hard to even know what kind of thing you're looking at when you're encountering a new thing for the first time. But the problem is also compounded by some of Clojure's more distinctive (and powerful) features such as ad-hoc polymorphism and using maps to pass function arguments.
The closest analogy from imperative languages that I can think of is another language that's famous for being incredibly productive in the hands of a skilled user, but whose code tends to feel kind of write-only: Perl.
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.
Because there's a preference for just passing immutable data around the time travel debugger really helps me understand complex interactions
Clojure is particularly vulnerable to that because it really is an enterprise applications language. That makes it more vulnerable to learning curve problems than a language like Rust whose most direct competitors are other languages with comparably steep learning curves.
nailed it. I don't know much about functional languages other than a haskell course back in the day, but I don't want to do functional all the time. It plays great in some situations and I am happy to use those language subsets in my daily coding, but I don't need my entire application to be written in an esoteric language
The selling point of Clojure is that persistent data structures prevent several classes of bugs (unintended mutation, locking, etc.). But in reality -- as long as your team members are good enough programmers -- I don't see these kind of bugs happen in practice.
That being said I love Clojure and the standard library is the best out of any language. It is a great choice in the small market of "projects that need simple and correct code".
It's probably fine most of the time it's just when I have to get into the weeds I want to have stability
Well, what else is overstated?
- Structural editing? Fine.
- REPL-driven development? Okay, let's throw that out the window.
- Hosted nature and the interop? Gone.
- Destructuring? Eh, we kinda have it in Javascript, right?
- Concurrency support? Who needs that shit, anyway, right?
- Simplicity and elegance? Arguable. Some like verbose Typescript code more.
- Functional programming? What the heck is it even?
The point I'm trying to make is that you can't just "remove" an essential part of what makes a language. Rich Hickey took a year-long sabbatical (or was it two or even three years? I forgot) and used his savings to get this aspect of the language right. Without the immutable collections, the language would've been an entirely different beast.
The day that Haskell stops being 10x slower than languages providing mutable data is the day we can start to seriously entertain your claims.
I only wish that the transient collections would support more different kinds of writes, like support all operations on java.util.List/Set/Map kind of thing. Forget which ones aren't present but I remember there being a couple...
As for Haskell, it does pretty well being 100 times faster than Python with 100 times less the number of people working on it.
The fact is that you Haskell people only talk about the optimizations that can sometimes open on immutable data in specific circumstances. What you generally ignore is the optimizations that immutable data permanently locks you out of with no recourse.
As with most things programming, immutability should be considered a tool, not a rule.
I'm actually quoting Prof Andrew Appel of Princeton: https://www.cs.princeton.edu/~appel/papers/ssafun.pdf
You seem to think that functional programming and in-place updates are mutually exclusive. This is not the case, e.g. Haskell supports mutation as a tracked and controlled side-effect. It can even give static guarantees that a function is pure even it uses mutation internally. Recent research even suggests that compilers can add the in-place updates for us: https://www.microsoft.com/en-us/research/publication/fp2-ful...
You can say SSA, static guarantees, internal mutability, blah blah blah all you want. When third party, not specifically chosen anecdotes to make FP look good, measurements stack up to the claims, we can have a better conversation.
It’s not looking good though, cause these claims of “actually, Haskell is faster than C because compiler magic” have been going on since well before stable Haskell like 15 years ago, and they’ve been lies for just as long.
That is working under the assumption that “the benefits” are actual benefits.
IMO, “the benefits” are measurably drawbacks, and not a technical debt I am willing to accept for no good reason.
in my opinion competes with python when it comes to DS/ML. I find it a lot more comfortable to use if you use emacs bindings
It uses XTDB by default but you can switch to Postgres: https://biffweb.com/p/how-to-use-postgres-with-biff/
People don't really use ORMs in Clojure, they just write SQL directly and abstract the details from consumers using functions. That said, HoneySQL is a common alternative to writing SQL that makes it a lot less painful (and composable!): https://github.com/seancorfield/honeysql
Edit: Not trying to dismiss your concerns, by the way. In Clojure you can often get away with doing less than you might think so I'm genuinely curious about the critique.
I don't need to define driver boilerplate for every query (or ever have to write it... at all).
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.
Thanks, that's neat.
I'm not even talking about the framework part. Just db access. Let's say I have a Posts with a managed_by property that points to a list of User which have a ManagedProfile. In Django's ORM (or any good ORM), I could do:
if post.managed_by.contains(user.managedProfile)...
or I could do:
post.managed_by.add(user.managedProfile)
also all these tables and join tables are generated by just a few lines of model definitions.
I'm still in control. I am writing the code. I get to choose when I do slow and fast stuff. Not having these features isn't "more control" it's less features. :P
I still see the benefits of Clojure, though!
And I agree with you, it's less features, and maybe it would be nice to have something similar in Clojure, but there's a reason the feature doesn't feel as needed, and nobody bothered building it.
In OO langs, one of the major pain points the ORM solves is mapping the result back into an Object. Otherwise, you have to manually go:
In fact, some ORM keep to that only, I think are normally called micro-ORMs. All they do is data mapping to/from between the DB and your object model.In Clojure, you don't have this pain point, the DB query returns a list of maps, and you work with those maps directly.
I suspect this is the main reason why no one bothers implementing an ORM-like in Clojure.
That means, for your example, you would create a function that queries the DB to check if a post is managed by a particular user. And you'd call that for your condition:
Or to add one you'd do something similar, create a function that adds a user to manage a post: You're working directly with IDs, because there are no Objects here. You're working with data directly, and that data is similar to the data in your DB, the representation is much closer between what your code uses and the DB.That means, in OO langs, you think of your state as being those object models, and then you try to sync them back/forth to the DB, after you've mutated it a bunch, you call .save() on it for example. But in Clojure, you think of your state as the DB itself, if you want to change state, you just run a query on the DB to change that state directly, you don't modify some in-memory model and then try to sync that back to the DB.
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.
Some will say this is a good thing (and as a very experienced engineer I will agree) but the barrier to entry for a newbie is quite high in this regard. You have all the rope to hang yourself with - and you will. Especially if you are on a team full of lots of interns and junior folks.
webserver: ring-jetty9-adapter[0]
routing: reitit[1]
html templates: hiccup[2]
never used an ORM, but happily used HugSQL for making composable queries[3].
[0]https://github.com/sunng87/ring-jetty9-adapter [1]https://github.com/metosin/reitit [2]https://github.com/weavejester/hiccup [3]https://www.hugsql.org/
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)
Plus, there are not a lot of semi-colons and commas in JS and Python.
Of course, there are constructs such as `let` and `cond` that are more parenthetically noisy (not so much in Clojure though), but on the flip side you don’t have to remember a lot of special syntax like in non-lisp languages.
Most Lisp-people also use structural editing tools like paredit[1], which make it really easy to write and edit s-expressions. I found that after some time I didn’t really think that much about parentheses anymore.
[1]: https://paredit.org
The lang itself is good and I recommend folks use a LISP sometime. I was just genuinely surprised a 17-year-old lang was lacking in these areas, and I'd be pretty careful about setting out on a long-term project with it, unless all of those things have radically improved from 6 months ago.
(And like other folks said, you genuinely need editor integrations to not be wasting all your time on pren balancing. Not clj's fault, just LISPs in general.)
A couple years with Rust has taught me that intuitive errors and tooling will funnel you far enough into language to get you productive, and then you're much more likely to stay. There's just no way I would have stayed long enough to be a Rust professional if it hadn't been for cargo and rust-analyzer.
These "non-language" components of Clojure are just not easy enough to use, and its inhibited Clojure's growth. If, however, you do put in the time to grok these parts, the joy of using the language itself never fades.
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?
Yes, it would be good if there were javadoc on more of the impl, but this is just not an issue for the vast majority of devs.
Tooling is a combo of Shadow CLJS and Clojure CLI.
[0] See `resources/public/fonts` here: https://github.com/eliascotto/tryclojure/tree/main/resources...
[1] https://github.com/thheller/shadow-cljs
[2] https://clojure.org/reference/clojure_cli
Compared to Go, it has a weak stdlib, is more memory intensive, and generally slower. You get beautifully concise code but it can be very hard to follow (or return to after time away).
Go can look completely idiotic or unbelievably focused and practical, depending on the light. It is painful to give up Clojure’s very strong selling points but I find alternatives to be more pragmatic.
"it can be very hard to follow": I agree, if you are not a lisp developer it's really hard, but if you develop clojure everyday, it's as easy as anything else.
The answer is usually clean code when it comes to clojure. Keep your functions small.
"more memory intensive, and generally slower": yep, the JVM is more memory intensive than Go. No surprise there and Clojure adds up on top of that. Startup times are pretty slow too. But compared to python it's still fast. Apples to oranges.
I would say Clojure and Go are both great languages that tackle different problems so it's not a fair comparison.
Regarding difficulty reading code, I'm not talking about syntax, I'm talking about the fact that many libraries define their own DSLs and you _cannot understand what they are doing_ by reading the code, you have to dig into the library internals to make sense of how they actually work, particularly when documentation is lacking or outdated.
Many problems can be solved by either language, and I wish it was easier to justify reaching for Clojure.
The contrast ratio here is 3.9:1 (text #DC2626, background #E5E7EB), and the minimum recommended for small text is 4.5:1.
Sorry to be that guy :) I am enjoying the tutorial.
1. When I read about Clojure (repeatedly) I like it. I am especially interested in transducers. I even built a half baked transducer engine in JavaScript using generators. There is clearly something here of value here. Any system that can produce transducers is worth learning.
2. I spent too many years of my life on Java. It was okay until someone wrote a thing in php (!!) in a day that would be have taken at least a month in Java. Maybe two. I understand the "good" of Java, but for me personally, the cost of that good is just incredible tedium. This is personal and I understand that.
3. However, as soon as I get to the part of installing Clojure where you install a JVM, I start hearing voices that say "It ain't me babe" and "Just say no to Java". PTSD?
For a while there was Clojure-script and I started working with that, but it seemed to fall off the edge of the world.
I will happily try Clojure again if it will compile itself or if it does not require the installation of a JVM. [ And last but not least of irrational reactions- anything associated with Oracle is to be avoided like the plague. sorry]
[Edit: then I saw the stuff about Babashka so I will give that a try]
I've been using Clojure heavily for the last 9 years or so and I can't see any reasons to dislike the JVM. Also, I barely ever touch any Java. You don't need to.
This means that you really don't need Java in order to use Clojure.
Coming from a C++ background, I used to dislike the JVM out of principle just like yourself, especially since it needs to run on my local machine and it uses so much memory and is slow to start up.
However, once your app is started, you're in the REPL and that's the only time you need to start your app.. You can keep developing for days without restarting your app once.
Once I finished developing my app and I deploy it to a server, I'm kind of happy it runs on the JVM - that thing is super tuned, very fast and runs on a myriad of hardware platforms.. I don't have to spend one minute thinking about those details.
So while the JVM is somewhat inconvenient on the dev machine, it helps a lot when you deploy it to production.
Things became a lot simpler when I stopped worrying about it and just used the language for its power and beauty.
I’ve done this with internal libraries - it’s easy to get them pulled into a Clojure code base, wrap them in such a way that the ergonomics of the rest of your code aren’t ruined, and still treat them as a first class citizen for stuff like your build system, artifact stores, etc.
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/
CLR: https://github.com/clojure/clojure-clr
LLVM: https://jank-lang.org/
Erlang: https://www.clojerl.org/
Python: http://hylang.org/
You understand that this has everything to do with Java and nothing to do with the JVM, right?
Really curious to hear what that was, because I'm having a hard time believing that is true.
[0] https://github.com/PEZ/rn-rf-shadow
As is it basically just shows you how to do arithmetic, which isn't interesting at all.
- Dependency management: https://clojure.org/guides/deps_and_cli
- Clojure->JS compiler: https://github.com/thheller/shadow-cljs
- React integration: https://github.com/reagent-project/reagent
- Global state management (optional): https://github.com/day8/re-frame
You might also want a CSS framework. There are some options to write CSS in ClojureScript, but I prefer TailwindCSS which isn't a Clojure-specific thing and it works fine out-of-the-box with `.cljs` files.
You can swap out Reagent for Helix, which is a lower-level and faster wrapper for React. That said, Reagent does work with React 18 just fine and there's tons of docs for it, so jumping to Helix first is a premature optimization IMO, especially if you're new to Clojure.
> "3(1 2)"
Yup, I'm out.
If you try that in Clojure, you'll get:
> ClassCastException class clojure.lang.PersistentList cannot be cast to class java.lang.Number
If you try it in a "proper" ClojureScript dev environment (shadow-cljs), you get nil as a result with this warning:
> cljs.core/+, all arguments must be numbers, got [number cljs.core/IList] instead
This is one of the real problems of clojure: you need some knowledge of the host language. It's also one of it's major strengths, and the only reason a lisp managed to get so much (relative) commercial traction.
(reference to: https://www.destroyallsoftware.com/talks/wat)
1 and 2 are integers. '(1 2) is a list. None of them are strings, but + helpfully converts them to strings because ClojureScript is leaking some of JavaScript's wat behavior.
https://youtu.be/et8xNAc2ic8
The same under Clojure (on top of the JVM) shall throw a class cast exception.
https://www.destroyallsoftware.com/talks/wat
If you need method autocomplete scoped to a type you can use the `..` macro for more concise call syntax:
this other form will a also autocomplete everything after the dot (.) to call a method on an object: but the trick with this arrangement is to write that variable some-str first, then if you write . in front of it the autocompletes will be relevant to that object. But I gratuitously used threading macros like .. or -> to make it match java-style code (in other words: (-> some-str (.toUpperCase))).For JVM interop, I think Cursive (IntelliJ Clojure plugin) is smart enough to help with autocompleting Java libraries.
I can't believe that after all these years Java still didn't fix their startup time.
[1]: E.g. see https://spring.io/blog/2023/10/16/runtime-efficiency-with-sp...
Im yet to reach the X-men level super qualities that can detect, and work in 40 ms chunks. Or at least even notice a 40 ms delays.
I envy the humans who can notice such small chunks of time.
git- I take more time to write the commit message to worry about 40 ms.
I mean sure optimise code to run it fast. But its not something that a human notices.
Isn't the initialization procedure (or at least the vast majority of it) exactly the same at each run ?
That said, there are alternative runtimes that have different tradeoffs. For example, Babashka is a runtime for Clojure that uses GraalVM instead of the JVM as the foundation. Babashka scripts have about a 10ms startup time on my M1 MacBook Air.
[1] https://clojure-goes-fast.com/blog/clojures-slow-start/
[2] https://clojure.org/reference/compilation
In some sense it's Clojure's fault for having an implementation that causes slow JVM startup, but it's also the JVM's fault that the way Clojure uses it causes it to take a long time to start.
So it is indeeed a Clojure issue, not a JVM one.
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).
https://source.android.com/docs/core/runtime/configure
AppCDs in OpenJDK currently has terrible ergonomics. Clojure can't really offer it. Each user must go out of their way to leverage it. So you can't really release an app that automatically leverage it, the user needs to launch it with all the command incantations, etc. And it's so sensitive to class path changes, etc. It kind of sucks to be honest. But some people still use it for prod release, since you can set it up in a docker easily. But the use-case for fast startup are desktop apps, CLIs, scripts, etc. And for all those, AppCDs are super annoying to setup. See: https://ask.clojure.org/index.php/8353/can-we-use-appcds-to-...
Still, AppCDs don't fully solve the startup issue, because all the static initializations stuff takes a considerable amount of time, and that does not get cached by AppCDs.
But before Babashka, that was indeed a barrier to using Clojure in shell scripts. Now we have it all!
I feel a good fraction of code will dip into Java libs at least a bit - so you're limited in what libraries you can use
I think the real solution is probably Graal native - though it's not part of the official toolbox/deps.edn
It's also possible to compile your JVM Clojure program yourself to a binary with GraalVM for even better performance than Babashka and even faster startup.
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.)
It's how much code you load.
E.g. any Python project has to deal with this, like Mercurial.
Java projects tend to (insert stereotype) have a lot of code.
No operator precedence rules to remember.
Use math operators in higher order functions:
Simpler parser in the language.Use kebab-case variable names.
Use most symbols in variable names.
The last two are because of reduced ambiguity in the language. The benefits for readability can be huge. For example, I love question marks for predicates in Scheme.
I'm sure there are more benefits that I haven't thought of.To be fair, there just needs to be syntax for passing infix functions as a "normal" function, like parens in Haskell
And to allow `?` in names, it "just" shall not be used elsewhere.As an added bonus, you now know Lisp. This is how all Lisp syntax works: `(function arg1 arg2 ...)`. That's literally it.
Incidentally, people used to be /really/ against Python because of its use of significant whitespace. The number of people who bounced off it because they would try to type something and then get a mysterious syntax error that turned out to be that they didn't type space or tab the right amount of times! It used to be what people would immediately post whenever anyone said anything about Python.
I think now people get introduced to IDEs at the same time as Python, or something happens at that early teaching moment that gets them over that hump. The same, in theory at least, happens when you play with a lisp long enough.
The main one being if you want to add many many items together it's much easier IE (+ 1 2 3 4 5 6).
Instead of doing an operation between two numbers you are apply a function to a list of inputs. In the "+" function's case you are adding them together. With this in mind, it's no different than most other programming languages. You would never have your function be in the middle of your arguments/inputs.
In python its Foo(bar, bar) and not bar Foo() bar, which obviously doesn't make sense.
The real advantage of this notation is that it's much, much easier to stack calculations.
Example:
(/ (+ 34 68 12 9 20) 140)
You can imagine how the first part of that could come about:
(+ 34 68 12 9 20)
And then the second part (pseudocode):
(/ sum 140)
In Clojure it's easy to mash them together, or for example to paste the first calculation in the second.
(/ (+ 34 68 12 9 20) 140)
Want to go further? Easy: add another enclosing parens.
(* (/ (+ 34 68 12 9 20) 140) 1.5 2)
Note how we're stacking in a more human-readable order - a new calculation starts on the left hand side, the first thing we see as we read it.
Compare how verbose the alternative is:
((34 + 68 + 12 + 9 + 20) / 140) * 1.5 * 2
The threads example in another comment is way better
The problem with that is by the time I reach `20)` I have already forgotten what operation I'm in. I'd write it more like this:
actually I'd write this as > ((34 + 68 + 12 + 9 + 20) / 140) * 1.5 * 2Why not (34 + 68 + 12 + 9 + 20) * 1.5 * 2 / 140?
If you work with clojure a lot, does it become natural?
Where it really shines though is when you use the threading macro and inline comments to make really complex math a breeze to review:
99% of the syntax is just (foo arg1 arg2).
(-> (+ 34 68 12 9 20) (/ 140))
(Some Lisps have interactive modes where you can drop the outermost parentheses, allowing you to type like this:
but usually the parentheses are part of the formal syntax; where this is just a hack in the interactive listener.)The design choice came about because the syntax started as an internal representation for a symbolic processing (formula manipulation) system that was being designed by John MacCarthy. The internal representation where the operator is first followed by its arguments was convenient because you don't have to parse around to identify them. You know immediately that by accessing the first position of the formula, there is going to be a symbol there which identifies its operator.
The internal form, and its written notation came to be used directly, while the symbol manipulation system came to be programmed in itself, so that its own "formulas" (source code) ended up in that form.
[1] https://www-formal.stanford.edu/jmc/recursive.pdf
I know it's really hard to see how. (And honestly it's sometimes a little hard for me to see what the big deal is, but one of my first programming languages was a dialect of lisp so I can barely even remember not being comfortable with them.) But many people find that, once they get used to them, and especially if they learn to use an editor with a paredit mode, they can start to feel even easier to work with than infix syntax.
That allows for some nifty tricks like:
(reduce + [1 2 3]) ; returns 6
(In my functional programming languages, `reduce` also requires passing an initial value, like `foldl (+) 0 [1, 2, 3]`. But surely it should get a monoid instead of two separate arguments for the operation and the starting value!)
+: https://github.com/clojure/clojure/blob/clojure-1.11.1/src/c...
*: https://github.com/clojure/clojure/blob/clojure-1.11.1/src/c...
Docs:
+: https://clojuredocs.org/clojure.core/%2B
*: https://clojuredocs.org/clojure.core/*
It's just a textbook monoid: an operation on two elements which produces another (+ or *), with a "neutral" element provided (0 or 1).
[0]: https://en.m.wikipedia.org/wiki/Polish_notation
(some-function-name arg1 arg2)
"Technically you can configure our product to do what you want" is a great way to have shitty defaults and to ignore all criticism you don't want to heed.
Also this is not a lisp critique, it's a clojure criticism. I get that lisp is simple, ancient and set in stone. Clojure or whatever layer on top of it should be defining this basic stuff on top of lisp.
Are we expected to build a 3rd layer of lisp on top of lisp? And then what pass the burden to our users and let them define a 4th layer of what they actually want?
We need opinionated developers who choose a set of values or industry or application, not indecisive devs who want to support every user.
Is it the stuff in your grandparent comment? That there should be some binding for x out of the box so that (x 1 2) does something rather than error out?
A lot of mainstream languages use * for multiplication, so that would just be inconsistent.
I also don't agree with your strange requirements where you propose that the character ÷ (U+00F7 division symbol) be used for division, whereas for multiplication you are proposing the plain lower case x letter from ASCII (U+0078).
What you want to pair with ÷ is the actual multiplication symbol × (U+00D7).
Separately from that inconsistency, it's probably a bad idea to introduce one-letter like x into the standard library of a language, particularly one that is Lisp-1 (one namespace for functions and variables).
Those math symbols ÷ and × are both hard to type (does not appear on keyboards and not supported nicely by common input methods) and easily confused for x.
> We need opinionated developers
Okay: Programming languages should stick to ASCII, get off my lawn.
Supporting Unicode in identifiers is fine: give that to the application programmers to use in their programs, if they are so inclined, but for Pete's sake, keep it out of the language and libraries.
(Some Lisp authors disagree; for instance, there is support for lambda being written using the actual Greek symbol in some Lisps.)
Personally I don't feel this strong urge to tell communities that I'm not interested in them. Waste of my time and theirs.
If you need to do graphql in typescript, you still "learning a new language". If you're using Prisma or you doing css-in-js, or rxjs, or tRPC, or Cypress, you're still "learning a new language".
You're basically moving from having to understand one (seemingly arbitrarily composed) syntax to another (seemingly arbitrarily composed) syntax. While I learned Clojure syntax once, and now only have to learn paradigms and techniques - logic, pattern-matching, concurrency, reactive dataflow, polymorphic dispatch, metaprogramming, event-driven programming, state management, etc. etc.
My hope is that I never have to use TypeScript, Go, or Rust daily since Clojure already gives me everything I need to write programs. It simply makes much better sense for the type of programs I'm writing today. Someday that may change, but I doubt that any of those would suddenly start making better sense to use. It probably will be something else, not these.
Don't feel like being the Blub programmer and missing out on new good stuff.
> If you want everything to be familiar, you'll never learn anything new. - Rich Hickey
My hot take -- There are already too many programming languages in use and every engineers life would be easier and productivity would be higher if we standardized.
There is almost no situation where making an entirely new language is the optimal solution. And the worst reason of all to make a language is anything aesthetic (e.g. spacing, parentheses, notations) -- all of those cases should be handled via native transpiling in the IDE.
There are some good examples of applications that are specifically written in Clojure and would be quite difficult to replicate in other PLs - Roam Research and Logseq, XTDB and Datomic, Nextjournal and Precursor.
> And the worst reason of all to make a language is anything aesthetic
Interestingly, people avoid learning Lisp, because it doesn't look "sexy enough" the first time they see it.
Every year dozens of people think they're going to make some holy-grail language and it's 1% different from the prior version but now every single library needs to be rewritten, a dozen years of patches/bugs/security-holes must happen, whole resumes get shined up rewriting tech stacks that were perfectly capable, all for it to be thrown for the next shiny toy in less than a decade.
A new language just fractures knowledge.
It sounds like you're trying to draw some equivalence between multiple spoken languages (largely seen as something brag-worthy-ish) and multiple programming languages. Like if culture thinks it's sexy to know french that somehow that means it's sexy to know Cobol [It's not].
The two are wildly different of course, a programming language can be learned in weeks and a spoken language takes years.
But if your question is -- would the world be a better place if we all spoke the same language? Then yes, absolutely.
Fortunately AI will help continue to reduce these artificial barriers between languages.
Edit: And no, "AI" is not going to affect any such barriers. It's already trivial to learn new natural languages, and some people do, some don't. Those who don't, generally don't care about people that speak other languages than the one they learned in childhood.