What about Clojure can be improved? [Not CLJS]

I’m worried I’ve become blind to issues that plague Clojure, as I’m not really able to answer these questions:

  • What about Clojure can be improved?
  • What would I change about Clojure to make it better?

I can think of only a few minor things:

  1. Error messages could be more indicative of the actual cause and the exact line of code.
  2. A few functions could have had better names, like contains? could have been contains-key?
  3. More things in core could have been protocols.
  4. Certain quirks related to Java are annoying, and it be nicer without them. For example, the forced directory structure of namespaces, the file names being underscores, some things in the REPL can’t reload like defmulti, classpath is confusing, reliance on exceptions, utf-16 instead of utf-8, etc.
  5. A few things could be removed, that are mostly deprecated, defstruct and the use declarative for example.
  6. And some really nitpicky edge cases could maybe be smoothed out, like a mode to disable chunking on lazy seq so when you need true 1 at a time lazyness you could get it easily. If without an else branch could be an error to force the use of when. Case and cond could have used the same syntax for their default branch. An easier way to avoid boxing when doing numeric math. Things like that.

And I can think of a few more major ones:

  1. Startup times are slow, and could be made faster.
  2. Overall performance could be improved, especially memory use, and reducing garbage.
  3. Overall tooling could be made easier to get started with.
  4. More compile time errors could be reported by the compiler, like the things clj-kondo reports on, and rudimentary type errors could be caught a la SBCL, there could be a warning if a lazy context has escaped certain bounds, etc.
  5. A bit more manpower behind it, so we could see more activity on core.async, core.logic, etc.

That’s about it from me. I really had to scratch my head hard to find these, which makes me feel pretty good about Clojure, but I’d be interested in hearing other’s thoughts on this as well, so what about Clojure can be improved and what would you change about it?

P.S.: I’m focused on Clojure only for now, so no ClojureScript or ClojureCLR, etc…

5 Likes

I think Clojure should add this Condition System to the core:

I would also like to see some Clojure adoption of the new Java Fibers for extremely lightweight threading.

I would also like see have more documentation around how agents can be used for massive numbers of state holding items with respect to how agents are usable like Akka Actors. I conjecture that agents are lightweight and a million or two can be created easily in a program when simulating large numbers of user tasks, etc. But I haven’t see anything good that really describes using them that way.

I’d like to see Avout picked up and supported more, b/c I think Clojure and Avout provide a amazing case for how to deal with large distributed state spaces that need to be transactional. I think many problems that need to be distributed and transactional are easily done with Avout and it should get more support than it has.

I would like to see more examples of Stream processing using the Core Clojure capabilities and libraries. Stream processing is a big topic and I think functional processes are the defacto solution, but their don’t seem to be a lot of examples of this usage for people to learn from.

Love to see the documentation efforts merge into one and that it be super rich, linkable, have the ability for people to try examples in place, for links to StackOverFlow to be referenced, for GitHub, GitLab and others to be linked.

Like to see the idea of Predicate functions and their use called out more as a concept in Clojure.org site like it is here: https://www.tutorialspoint.com/clojure/clojure_predicates.htm

I’d like to see REPL objects become richer in the sense that the REPL would have some type of pop-up menu that would let you go to docs for current symbol/form, eval current symbol/form, and other things that are context functions applicable to current symbol or form.

That’s a few from my list…

Sam Griffith

3 Likes

Detailed lists like this are useful as we start to look at 1.11 focus, so happy to see more. @didibus some comments on yours:

Minor:

  1. We did a ton of work on this in 1.10.x. I would love to hear if that addressed 10%/50%/90% of the issue and specific cases where it did not (ideally with tickets or questions on ask.clojure).
  2. Names are what they are, minor naming issues like this are not going to change.
  3. Sure, but again, this is impossible to change now without breaking everything, so not going to happen.
  4. Some of that is just price of Java/JVM interop admission. Probably nothing there we’re going to do.
  5. Again, would cause breakage, not going to do it.
  6. If you need true 1 at a time laziness, you should not use laziness. Use loop/recur, transducers, or something else where you have more control.

Major:

  1. I will continue to preach caution about this as “one” problem. There are multiple use cases here with different constraints and different solutions: a) non-AOT dev-time REPL startup, b) AOT’ed app startup, c) serverless lambda fast startup, d) tool/script startup. I would probably say a or c are highest priority of those (b don’t care as much, d has some options like bootstrap cljs, graal, etc).
  2. Always could be better. How often is this a limiting factor? (I see it occasionally, but not frequently. ram is cheap)
  3. For sure
  4. Probably interesting
  5. I would be happy to see more manpower - can you provide it? core.async has a bunch of hard patches that need screening. core.logic is available if someone wants to take over as a maintainer. etc.

We’ve definitely got a few other itches we’re thinking about and I hope to solicit some feedback on them re prioritization soon.

5 Likes

@staypufd

  • condition system - exceptions are the canonical answer here for Clojure, not going to add it
  • Java fibers - won’t be available till Java 14 or later so we’ll look at it in a few years. :slight_smile:
  • Agents - most systems are difficult to structure as agents so few people make use of them. Simulation systems is really the sweet spot, but that’s not a very common use case.
  • Avout - ran into some road blocks. It’s very difficult to retain the same stateful semantics in a distributed system that you maintain in-process, particularly under the jvm’s constraints.
  • stream processing - that would be great. Seems like a lot of people have done Kafka, Onyx, etc though. What’s lacking?
  • docs - sounds great, make it so :slight_smile:
  • predicate functions - file an issue at https://github.com/clojure/clojure-site/
  • REPLs - repl all the things, would love to see more
6 Likes

Thanks Alex for the detailed reply.
Bummer to hear about Avout issues but what you said helps me understand why.
For stream stuff I meant more tutorials of a beginner nature that explaining the differences etc of using them vs a Event Bus, SOA, or queueing system.
Docs and Repl I have actually been working on some things of the nature I described. Readline is a good example for sure.

Again, thanks for the reply and hard work everyone involved has done to get Clojure to where we are today.

Sam Griffith
staypufd

1 Like

Have you looked at rebel-readline or even just Unravel/unrepl with Compliment enabled? Those provide docs as you type.

Not sure what you mean about “eval current symbol/form” – that’s basic functionality in editors that integrate with a REPL (as are docs, jump to definition, and many other things).

I’d also suggest looking at Cognitect’s REBL which provides dynamic uses of Vars, navigation of namespaces, and a bunch of other useful stuff. I’ve posted a couple of videos showing my use of REBL if you want to see how it works “for real” https://www.youtube.com/channel/UC8GD-smsEvNyRd3EZ8oeSrA?view_as=subscriber

1 Like

It’s a really difficult question for me. I sympathize with most of your points but I’m not sure I feel strongly enough about any of them to complain about them as problems with Clojure. Several of them can always be improved – some of them have been and some of them will continue to be improved. But I think that’s true of any tooling you use: there is always room for some continual improvement.

At this point, after nearly nine years of using Clojure in production, I’m rarely bothered by the error messages or the startup time, and I’ve stopped being bothered by some of the odd naming quirks. It would be nice to see some things evolve a bit more quickly – I tend to work on the leading edge of Clojure builds so I’m eager to see Spec 2 settle down as a “core” part of Clojure, I’m looking forward to some variant of add-lib becoming “standard”, I’d like to see REBL advance a bit faster. Those aren’t things that the community can really help with (except by testing and providing feedback) since they are dependent on Cognitect bodies. For the Contrib libs, the community could help by stepping up and volunteering to maintain ones that are Stable or Inactive (I just took over clojure.java.data so I could fix bugs and enhance it, since one of my OSS libraries depends on it).

I think the biggest improvements can come in tooling. We have a large codebase at work (90K lines) and having tooling to find “similar” pieces of code or assist with organizing namespaces for cohesiveness and identifying coupling would be very useful. OOP has some natural self-organizing aspects (because functions pretty much have to associate with the data they operate on) that FP lacks, especially in Clojure’s world where your data structures – and the functions that operate on them – are often much more generic.

6 Likes

I’d love to see raw string literals for regex, grammar and similar things. Escaping can be very confusing.

2 Likes

I’d like to see some movement toward making accretion idiomatic, with reference to Rich Hickey’s talk about avoiding breaking changes. I mean at the application of Clojure, not the language itself which does seem to follow the principle (and is why I doubt fn names will change).

I don’t know if that would be at the language level (think darklang immutable fns), a support library, a clojure-ish github that doesn’t support mutation, or just broader commitment from the community.

Some things I would be thrilled to see:

  • Improved interop with Java 8+ features: streams, functional interfaces etc. I know this is being looked at and I have voted for JIRA issue :slightly_smiling_face: (https://clojure.atlassian.net/projects/CLJ/issues/CLJ-2365)
  • Improved support for namespaced keywords, mainly an easier way to alias namespaced keywords without having to create an actual namespace for it (not sure if there is a JIRA issue for this?).
  • Simplified calling of varargs methods (https://clojure.atlassian.net/projects/CLJ/issues/CLJ-440)
  • Some invokedynamic magic on for example vars and multimethods for improved performance/inlining: I know Ghadi has looked into this a lot, but I’m not sure what the conclusion was.
  • Better support for working with primitive types other than long and double: I usually drop down to Java when doing byte-heavy stuff like crypto, it would be great if I could stay in Clojure-land for that.

There’s always room for improvement in any project but overall Clojure is far and away the most enjoyable programming language I’ve used.

3 Likes

@schme …great list, many things we are actively considering

5 Likes

@d-t-w we spent some time this week talking about this, for sure an area of interest, although not sure yet where this may lead

1 Like

@cljrt raw string literals has been proposed and declined several times over the years. The value for cost ratio does not seem favorable to us so I think this one is unlikely.

Is there an issue for the client-side AOT-cache of libraries? This was discussed some time ago in clojure-dev slack. Would like to see that moving forward.

Currently, despite REPL spins up fast, all libs are distributed as sources and get re-compiled every time when they are loaded into REPL adding easily 10+ secs for just to spin up a batteries included web server (or a test runner). Caching compiled classes for libs that haven’t changed between REPL sessions would help a lot.

➜  ~ clj -Sdeps '{:deps { org.clojure/core.async {:mvn/version "0.4.500"}, manifold {:mvn/version "0.1.8"}}}'
Clojure 1.10.0
user=> (time (require '[manifold.stream :as s]))
"Elapsed time: 7715.211058 msecs"
4 Likes

If I understand @didibus’s intent, it was to have a philosophical discussion about what a dream world version of Clojure might look like, not to make any sort of request for changes to the actual, real world language.

Working from that perspective, I agree with @didibus regarding error messages, some naming, greater reliance on protocols in core, and with @staypufd that a proper Common Lisp-style condition system would be a feature of such a dream language. (Rich told me that the condition system is also one of the things he misses from CL).

Starting from the understanding that I find Rich’s taste in programming language features superb and both understand and respect the places where his tastes differ from my own, here are a few of my preferences:

  • the core language and the standard library in clojure.core would be minimal, with more stuff in other namespaces, both because this allows finer-grained choice of behavior and because it makes it easier to change things later without breaking anything
  • sequence/string/whatever functions would take the parameterization first and the sequence last so they are super easy to partial. (Core prefers to align the params based on -> and -->, which is also perfectly sensible)
  • the core language would be eager by default with optional laziness, as one sees in OCaml, where said eager sequence functions return a sequence of the same type they were passed (if reducers and transducers had existed earlier, I suspect this might have been true)
  • although I feel the design decisions of the current compiler were the correct pragmatic choices for the situation in which they were written, in my dream we’d have a mostly-in-clojure compiler along nanopass lines where various backend targets can be addressed by porting the first few low-level passes, thus making porting to new targets easier and allowing improvements and optimizations at the higher level to be automatically shared between those implementations (JVM, JS, CLR, native)
  • speaking of the compiler, it would be grand for it to have something akin to the Nimble type inference algorithm; mostly for performance, but also to improve early detection of obvious type errors in code
  • consistent printer and reader behavior for all program values, allowing serialized/unserialized to EDN (admittedly, this is somewhere between very hard and impossible on the JVM)

Climbing back down from pie-in-the-sky: of the literally dozens of programming languages I’ve used professionally over decades in the trade, I currently reach for Clojure as my default daily driver with a smile on my face. :blush:

4 Likes

I think I created an opening for both. What could be improved can be seen as something that 1.11, 1.12, etc. could address. And what would you change is more about a hypothetical Clojure 2.0 dream like Clojure reboot. My own list I think mixed and matched from these two categories.

I think both are interesting to hear and discuss. I’ve been enjoying quite a bit everyone’s thought. I like taking a step back sometimes, once you’re too deep into something, you lose perspective. So keep those perspective coming!! And thanks all who already participated.

2 Likes

Really happy to see serverless lambdas broken out as a priority distinct from startup time. Historically the startup time argument has centered around the development REPL experience (which I agree is great now), but lambda changes things a bit and Clojure struggles to keep up with the pack here (cold start for Java is ~200ms & Clojure is ~2s). Would love to see improvements in this space.

1 Like

I will pitch in with areas that I personally have problems with, developing a fairly large application. These are just vague areas, e.g. they are not well defined, because defining them is actually part of the solution. In other words, I do not know what I really want, I just know that these are problematic areas.

  • Java 8 java.util.function interfaces: I could use better support for those in Clojure (I do realize there is an ask.clojure.org entry, I upvoted it already). There is increasing friction when integrating with newer Java code that uses those interfaces.
  • java.util.concurrent (CompletableFuture and CompletionStage): similarly.
  • core.async: handling errors. This, I think, is a “Rich comes back in one year with an interesting solution to a problem I didn’t know I had” kind of work. I am struggling with implementing error handling in async code, ending up building custom protocols on top of core.async channels, catching exceptions from go blocks, and so on. I also think the built-in promises could use improvement. Java’s approach, where futures and promises can complete, complete exceptionally, or get canceled, is something to consider.

This is what I struggle with. The first two areas are less important: I just end up with more (mostly boilerplate) code. As I discovered, though, handling errors is a large part of the work when writing asynchronous code that isn’t just a simple demo, and that is a growing problem for me.

@ikitommi still open for consideration. there are some tricky things to handle but I am still optimistic that it could be a big win if integrated well.

1 Like

Just thought about this while answering the quarterly Clojurists Together survey, and came to the
conclusion that my biggest problem with Clojure is the low number of available jobs (here in Norway).

3 Likes