Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Clojure 1.11 is now available (clojure.org)
238 points by simongray on March 22, 2022 | hide | past | favorite | 69 comments


So thankful to be programming in Clojure the past few years. Thank you, Clojure Team, for all you do!

My favorite things about using Clojure: 1. Runs on the JVM. Battle tested, tons of libraries, and well engineered. 2. Immutable Data Structures. I wouldn't want to live without this safety ever again. 3. LISP syntax - so simple to remember and just basically code functions! I also find reading Clojure code very simple vs other procedural languages. 4. REPL development. 5. Transducers - not unique to Clojure, but just love them so listed it.

Great language.


When you say immutable data structures, do you mean all lisp data structures are immutable? It’s been awhile since I’ve worked with lisp (and that was implementing a MAL not actually writing lisp.)


Clojure data structures are immutable [1], core to its principles.

1 https://clojure.org/reference/data_structures


Ah okay that’s cool then. I thought you were calling out some low level implementation detail of lisp I had overlooked. I’m trying to build a lisp as the intermediate of my new toy programming language. So I wanna get that right.


No it's a Clojure thing. Other lisps may vary.


About the syntax. I tend to prefer Scheme because (, [ and { mean the same thing, whether in Clojure they are different constructs. How do you feel about this minor point?


I think the clojure syntax, with those symbols differentiated, is a pretty great choice.

The choice of default meanings is good too, lists/application, indexed sequences and k/v mappings are useful widely used primitive constructs (clojure makes sets (the math meaning, unordered collection, no dupes) with #{} which is handy).

It cleans up the code nicely, having these literals, and now I find non-clojure styles to be a bit annoying to read with the extra noise from contructor function calls.

And when describing data/DSLs using these literals people generally carry the general meanings over, so things can be a bit more intuitive. And in a lot of places just using ('s and ['s in a way that makes it easy to disambiguate them when reading some DSL or literal value also makes for nicer reading of code (just in terms of a bit less mental parsing effort).

I'm the type to not be immediately a fan of extra syntax added to a lisp, but I've been convinced that edn notation is pretty wonderful and I've been converted.


> (, [ and { mean the same thing...

That's not a feature of Scheme. It must be a feature of the implementation you use. (Which one?) E.g. in R7RS, [ ] and { } are explicitly reserved for future language extensions.

https://standards.scheme.org/official/r7rs.pdf


My last Lisp project was using Racket, but when it was called PLT Scheme. It's been a long time since then :)


It seems like only yesterday... but you're right, we are old. :)

I was never a big PLT user -- Chicken Scheme for life! -- but they have always been about pushing language boundaries, I'm not surprised to hear that they supported an extended syntax.


I guess for me, I like having them be different things, like they are in Clojure. But that could certainly be because Clojure is my first and only exposure to a LISP type syntax.


Glad to see Clojure continuing to improve. Racket borrows a lot from Clojure^1,2,3 so its success fuels ours as well. With the other post on here about Java 18 including Pattern Matching, I'm glad that good ideas continue to cross pollinate between different languages. Programming is still a young field compared to other professions, and there is still a lot of good things left to discover!

1. https://docs.racket-lang.org/collections/index.html 2. https://docs.racket-lang.org/seq/index.html 3. https://docs.racket-lang.org/threading/index.html


It doesn't just keep on improving, but stays backwards compatible with every new release!


Interesting to see racket and clojure cross pollinating. I wonder what other non mainstream language is part of this game (julia, factor, ..)


A lot of clojure has been borrowing from main stream programming languages that lisp users don’t think of using at all which is great.


Those are third party packages, of which only the threading package is popular.


Yes but people are still interested in it. For example in the Rhombus Proposal (Formerly Racket2) there is a section on generic collections, with a link to a blog post comparing Racket and Clojure from 2010.

1. https://github.com/racket/rhombus-prototype/blob/master/reso...

2. http://programming-puzzler.blogspot.com/2010/08/racket-vs-cl...


If you have been filtering out clojure due to dislike of JVM, give it a try with GraalVM either directly [1] or via Babashka [2]. GraalVM will in most cases not only run your code much faster than JVM, but cuts startup time from seconds down to msecs [3][4], not to mention compiling to native with c api or LLVM. With Babashka, it packages a subset of GraalVM so you don't have to install that but is more limited. So if you just want to play around with clojure to see if you like it, Babashka is a great way to start.

[1] https://github.com/oracle/graal

[2] https://github.com/babashka/babashka

[3] https://arnoldgalovics.com/java-cold-start-aws-lambda-graalv... (large proj - dynamoDB)

[4] https://dev.to/wololock/groovy-script-startup-time-from-2-1s... (small proj)


A nitpick: Your code will not run faster in GraalVM vs the JVM, it will start faster. If its small scripts where the startup time dominates, then of course it goes faster. For anything long-lived (even in the range of tens of seconds), the JVM has better performance.


To be fair, it might run faster as well, or not. It seems to vary from case to case.


I owe a lot to Babashka. Now I can use Clojure for real-job scripting instead of just playing for fun with it.

For a one-liner scripting I still use Bash but for a larger script I now use Clojure.


Juxt had a recent blog post covering the new Clojure (iteration) function in depth, which is useful for abstracting over sequences that require some state management to access, such as a paginated API.

https://www.juxt.pro/blog/new-clojure-iteration


Nice to see it keeping to improve.

Whìch happens to be the favourite alternative JVM language from Brian Goetz,

https://youtu.be/GedrGWu16_I


Much love to all the people that make Clojure such a wonderful language to use.

I'm particularly happy with the new keyword args semantics, which solves a long standing pain point.


Sweet, variadic maps sound really useful, I am always having to take a moment when mixing functions that take keyword arguments vs. option maps. Also, some of those functions added to core show up in my code as convenience functions - Glad I can now just make use of core.


I definitely have random-uuid defined in a half-dozen of my own projects that can go away now.


The parse-*, update-keys, update-vals, and random-uuid functions are great additions to core. I look forward to dropping the custom versions from each of my projects!


Every person I know that has developed in Clojure has high praise for it.

How does it compares to the likes of other statically typed Lisp dialects such as Rackett?

I've always wanted to read "Clojure for the brave and true" but there's just enough lisp books (my favorite being "Structure and Interpretation of Computer Programs") I can read without actually wanting to use the language.


I think a lot of people that come from clojure arrive at consensus that you can instrument some typechecking on edges. Define what is valid(input) data and move it through your program. Inside your program it rarely matters(from validity perspective) if something is a list, tuple, lazylist - it is a collection. Things are either single value, collection or associative the semantics around each in code are pretty obvious in most cases.

Its always contentious topic, there are no answers.


I do wonder what has to happen for greater adoption of Clojure.


- ease the onboarding. Every few months I try to give clojure another shot, and every few months _some_ part of the setup has changed and / or is broken.

- compile to small binaries that run fast. I get what the langage gets from the JVM, but those 5,10 seconds I get before _anything_ runs, even after I had everything compiled ?

- show me an example of how having 'spec' is going to help me refactor the code that I got wrong the first time, as easily as what a proto-ML-like static type checker does. It's not a question of "types are bad vs types are good thing". It's a question of "this property was called 'name', but now I need it to be 'names', and I really need to know every possible place of my code base that uses it so that I can recursively change all code paths to handle the fact that it's a list, now." I read the spec doc a dozen times, and I don't think it does help in this simplest of simple case.

Also, I make typos all the time, and caml / typescript / rust catch them before I waste a run cycle. Dynamic langages can't know if 'names' and 'name' coexist - Let me tell the compiler what I mean once.

- promote an "obvious" gui lib. It's not obvious what I need to use to do an hello world window (but that's really not specific to clojure, in all fairness...)


GraalVM has made creating Clojure binaries with quick launch times much more possible. I suggest checking out babashka (Closure bash scripting) as an example and as a means.


Does that also work on embedded platforms with limited resources?


No idea, I doubt it.

There is Ferret [https://ferret-lang.org/] if you want Clojure compiled to C++11.

For microcontrollers, I've always used uLisp or plain C.


> show me an example of how having 'spec' is going to help me refactor the code that I got wrong the first time, as easily as what a proto-ML-like static type checker does. It's not a question of "types are bad vs types are good thing". It's a question of "this property was called 'name', but now I need it to be 'names', and I really need to know every possible place of my code base that uses it so that I can recursively change all code paths to handle the fact that it's a list, now." I read the spec doc a dozen times, and I don't think it does help in this simplest of simple case.

For this, I think the way to go is to use `fdef` to annotate the arguments to each function. This is a lot more explicit keyboard-typing on the programmer's part, but then so is a statically-typed language.

Where I think Clojure will still come up short is in tooling to support finding each reference in a better way than grepping your project for `:user/name`. All the information you need is in there, as you can see from this proof-of-concept tool[0], but it's not implemented seamlessly into the workflow.

[0]: https://github.com/clj-kondo/inspector


Refactoring is still hard and awfully manual. And like all dynamic languages, a wrong key (was it :user or :users or :username or :user_name?) will be a PITA to find.


Refactoring from a :user/name String to a :user/names Array is going to be manual in both static and dynamic languages until Github Copilot gets a lot better. All the compiler or Spec can do is show you the places in your code where you'll need to make updates.


More precisely, it can show you "all" the places where I have to rework. And, since it is very likely that other pieces of the software will need to be change, it's crucial that is does it recursively.

The compiler can do that ; I'm not aware of how spec could do that.

Framing it as "all the compiler can do" us a bit weird - that's _exactly_ the thing I need.


> - ease the onboarding. Every few months I try to give clojure another shot, and every few months _some_ part of the setup has changed and / or is broken.

What are you finding that is breaking? Backwards compatibility is pretty stellar in the language


Probably referring to the docs. I myself am irritated at the pace of change of all the clj command-line options.

In general, Clojure still has poor documentation compared to more popular languages.


Also, about the 'name' -> 'names' thing ; I get that maybe the idea should be that I should keep the 'name' property around and just accrete the 'names' thing, and API should be immutable and all, but, just, 'No'.


Nothing. It is still growing. Slowly, steadily, systematically. Clojure evolves strategically, not tactically. Besides, no matter what happens - Clojure would stay a niche language. There are certain benefits to that. Those who tie business worth and technical advantages to language popularity, often ignore them.

My unit (just like many other Clojure teams) has been building numerous solutions for many years now. Our employer loves us very much. As long as we keep making money; maintain our codebase aiming for expansion rather than [quartely] renovation - investors won't care what we use to achieve the results. If other companies prefer hiring new devs and rebuilding everything every two years - well, that's their money.


Maybe a fuller understanding of its applicability to discussions like this one from yesterday? https://news.ycombinator.com/item?id=30753127


I would never use it under any circumstance of my choosing, since it's a dynamically typed language and I don't have any further need for any more of those at the moment; in fact only looking to cut down on my usage of them.

But from my experience working with it on real projects:

- Good tutorials, combined with library and build tooling integration for usage with GraalVM. Startup times make it a horrible fit for anything other than long-lived daemons.

- Speaking of build systems: more ecosystem coalescing around tools.deps, there's a lot of resources around Leiningen, not too many around the blessed native tooling (I know, I know, it's fairly new for Clojure standards, but it is a problem).

- More and better linters for popular libraries. Without good types, you need something to guide you when you're misusing an API; the team where I worked with Clojure lost a lot of time getting Reagent patterns wrong.

My final complaint is something that I don't think can't ever be fixed without undoing what makes Clojure, Clojure:

REPL-driven development combined with dynamic typing, in the same way as its cousin: debugger-driven development, often leads to write-only code that works but good luck using it or modifying it if you don't have good automated tests to tell you how it's supposed to be used and more importantly how it's not.

It doesn't help that tests are de-emphasized by the community precisely because of REPL-driven development. "Just put it on the REPL and see what it does" is not a good way of reasoning about code, and makes it easy and convenient to just pile on more hacks that may or may not break something instead of promoting understanding. "Just write good code and have discipline, you're doomed if you can't do that anyway" does not excuse that other languages have radically different tools and practices that lead to far fewer footguns.


>> other languages have radically different tools and practices that lead to far fewer footguns

Can you share any examples of these different tools and practices?


"Advanced" (read: decades old) type systems. They provide tools like algebraic data types, generics, interfaces, row polymorphism and many others, but just those four allow expressing a gargantuan amount of invariants in the type system without sacrificing flexibility. Invariants that can be proven once and be maintained throughout years of code churn without repetitive runtime checks, heavy discipline or overly repetitive automated tests that attempt to poorly reimplement a type system.

They can be used not only as a way to let the compiler help you out in not making mistakes but also as an exploratory tool to test out many edge cases with quick feedback and as an informational aid to help quickly introduce new developers into a codebase with its own business domain explained in a language that allows conveying "this is possible" and "this isn't possible" much more quickly than reading tons of procedural code until the reader comprehends all the implicit rules laid down by the program's behavior.


Well, respectfully (while not disagreeing with you), I have to say that you seem to live in some idealistic utopia of software crafting. Between the crazy world of gazillion lines of shitty Python or Javascript, and some incredible proof assistants, I think Clojure finds itself in a quite pragmatic, practical, and cozy niche.

Notably, Clojure "speaks money". Money necessitates reliability and requires defect-free software. At the same time, modern, digitized money management desires flexibility and efficiency in fixing bugs and adding new features; Clojure very often fits perfectly for it.

That is why it is so prevalent within fintech startups. If someone pays you to play around with advanced type systems, and you love that, what can I say? You are a lucky one. I get paid to build something that works. I'm not a zealot; I use Clojure because it makes sense (for me). But some people prefer Python and Javascript. And that's fine.


I believe fintech is mostly a functional hub. There's an above average amount of Haskell, F#, and Scala in it. Most of the Clojure influence I believe comes from Nubank initially starting out as a Clojure shop IIRC.

Unfortunately I also happen to not get paid to play around with languages, although I'm open to such a position if you know of any available one. I get paid to write robust software and to work with engineers that don't always have 10+ years of experience. I get paid to deal with requirements that change every bloody week because we have quick customer feedback, customers that are very vocal about wanting things a certain way and highly intolerant of software crapping the bed when they go and try their "super normal, and expectable" extreme edge case.

I've dealt with those cases across many languages across the years, and you could easily guess that I would be very desperate for the money if you find me in another job working in a dynamic language dealing with constant changes.

By the way, it's okay if you're not familiar with the concepts I brought up by name, but if you have worked with Rust, TypeScript, Haskell, Scala, Kotlin, PureScript, Elm, F#, Swift or one of many others, you're probably familiar with them. They're everyday tools to programmers using those languages, not some strange academic tool.


> I get paid to deal with requirements that change every bloody week because we have quick customer feedback

In my experience this is something where clojure is exceptionally good at.


It seems I have been very unlucky. Because mine was that hacks piled, and piled, and piled, and no one ever got back to them because we could just do something horrible with metadata and cond our way to no man's land, without any types serving as documentation for the mess that was being created.


Yep, just like I said in the adjacent thread: https://news.ycombinator.com/item?id=30771964


>I get paid to write robust software and to work with engineers that don't always have 10+ years of experience. ... > I've dealt with those cases across many languages across the years

I'm not trying to denigrate your experience, and I'm not pretending that we work in different industries and deal with very dissimilar problems. But if anything I learned from the years of writing software - there are no silver bullets. Every single tool has cons and pros. You can't just look at any instrument, framework, library, language through a tiny keyhole and make assumptions. And I'm not saying you have. Perhaps you have rich experience using Clojure that led you to the conclusions you made. From my perspective - I have the opposite of that. And again, it's not because I have not seen what you have. Clojure, in my opinion, compensates for the lack of static typing with a plethora of other features. Features that many other languages can't offer but are essential for me.


..... you realise clojure has one of the most powerful ways to 'type' your language? Like 100x more powerful than the typescript or whatever type language you are using now ;p

https://www.youtube.com/watch?v=VNTQ-M_uSo8


Clojure.spec is runtime analysis, not static analysis. They aren't solutions in the same space.


When I was working for a fintech company, we built a suite of specs for a ledger. Based on those specs, we could generate data. And it wasn't just some set of key/value pairs with completely randomized numbers. It would generate "a proper" ledger, where every number in a transaction depends on other transactions.

We used that generated data to render UI locally and on the non-prod environments.

Using the same specs we built data validators, we re-used the specs to validate data in the input fields in the UI, which is totally bonkers. How the heck do you achieve code re-use between completely incompatible ecosystems - in our case, JVM and Javascript? Even Nodejs doesn't always let you re-use code between the backend and the front. Clojure does.

Using the same specs, we've built property-based/generative tests.

Before, I never experienced the joy of creating such robust, predictable, and reliable software with any other (statically typed or otherwise) language.

I'm not saying you cannot build a similar thing (or even better) with Scala or Haskell (or some other PL). The simplicity of how Clojure allows you to write stuff like that - is just incomparable.

Once again, I'd repeat the point I made in the parent thread: Clojure has an excellent price/quality ratio for building software. ROI from hiring Clojure devs, in many cases notably higher.


I'm glad you found great uses for clojure.spec. We need more tools like it.

If you ever find yourself writing TypeScript I'd recommend you try io-ts, which fullfills the same role as clojure.spec but is also able to auto-generate types from the runtime codecs, which you can then use in your functions to make sure they are only used with data that passed decoding. Not having to rely on discipline or "pinky promising" that you have decoded data is a huge boon when changing code later, or understanding the full scope of a function.

This isn't unique of course to Clojure or TypeScript as you mentioned, but there aren't too many good libraries built on the concept, and it doesn't work well in all languages.

> How the heck do you achieve code re-use between completely incompatible ecosystems - in our case, JVM and Javascript?

Just to point it out, Clojure does this the same as everyone else and has the same issue: So long as you don't call runtime-specific non-portable functionality, you should be good. Plenty of Clojure libraries targeting the JVM do not work on ClojureScript and viceversa.

> Clojure has an excellent price/quality ratio for building software

I agree that Clojure is fairly competitive, so long as we're talking initially building software. I just find it that specially once maintenance, rapid requirement changes, junior engineers, and staff rotation is taken into account, either developer productivity or the robustness of the software falls off a cliff.


> either developer productivity or the robustness of the software falls off a cliff.

So, I once worked on a project for the retail industry. And the requirement changes weren't just crazy, sometimes they felt legit grotesque. Like for example: "hey guys, we have a new program - everyone in Nebraska and Ohio gets 20%, except these particular vendors. The discount should work only for their customers if they are buying these specific items. We're going to need to unveil this in four days. Get to work." And then on the day of the release: "oh, remember about specific items? They want to expand it to a specific category of items. Can you make a quick change?" An hour later: "actually, we need to keep both, marketing thinks we need to A/B test this..."

And it was like that all the time. We had numerous discussions within the team, each of us had rich experience working with multiple other languages before. I'm not gonna lie - a few times we've said: "if we had a static type system...", but in the end, we all agreed - it would've been challenging to maintain the system the way we did if we had to use something else than Clojure.

In any case, all this remains a personal opinion. Yes, Clojure isn't perfect, but right now, it's quite suitable for my needs.


One thing: Rich Hickey has to reject the ecosystem-killing mindset that he summed up in his essay, “Open Source is not about you.”


If you have ideas on how to improve Clojure adoption, there's a #growth channel on the Clojurian slack server.


Companies seeing some value in it and pushing it but that won't happen. Most companies today want static typing and don't want to rely on discipline for stuff like maintaining documentation/more tests and having them up to date.

If it's true that engineers leave after one/two years then I can't blame them. Whatever cost static typing brings, it's worth it to them. Just look at how typescript usage has exploded.


It needs to run natively and not on the JVM. Performance is bad compared to Rust and even Go. Erlang is better when it comes to parallelism.


Seems like a weird nitpick, given that Clojure is a parasitic language by design. In addition to the JVM it also runs on GraalVM, Node, BEAM and .NET.


"Runs", on paper. BEAM and .NET are more of the same and not at all practical and GraalVM not what I mean by native. Also, clojurescript is not clojure.


I use Clojure every day at work and it's my favorite dynamic functional language. That said, I think in large projects like the one I'm working in, a statically typed language would've been better. I know Clojure has spec and other similar libraries but it's just not a replacement for static typing.

I love Clojure and would use it for small projects if given the chance but I think there are diminishing returns when you scale up, specially if you deal with a complex data structures which get encoded and decoded to JSON (messes up things like keywords).


The contributor list is shrinking :(


The contributor list is append-only. :)


The contributor list is expanding :)


The contributor list ain't list at all. It's an immutable collection.

I am so happy to see that not one but a couple of those names are my colleagues. They send patches to clojure.core. I send comments to HN :). I'm proud of them, I hope they proud of me too.


that's a pretty great comment!




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: