For the site author, thanks for this tool! Clojure has changed the way I program and I've been using it for 6 years now. Anything to help more people get into Clojure is much appreciated, especially if it's REPL-oriented.
For some feedback, you might consider using Let's Encrypt for setting up free TLS certs, or hosting on Github Pages to get it automatically. With just HTTP, my browser (and many others) will show a scary warning telling me not to use the site.
I have been using Clojure professionally for the last 2.5 years, after doing 9 years of C#, and really enjoy it. I recently got a Windows laptop and was interested in ClojureCLR. It has a ways to go in terms of coming close to tooling of JVM and ClojureScript. David Miller has done an amazing job at keeping the project going and I hope I can give back to that community
Awesome job. I have also made another Clojure interactive tutorial which is more advanced, even showcases how to use React with ClojureScript. It is available in https://orgpad.com/s/clojure-tutorial and I have made a video about it in https://youtu.be/lJxvKNjRdu8.
For those interested in Clojure in general, the Clojurians Slack is an amazing resource. Great community, and extremely helpful to folks getting started.
Part of what makes it a helpful is that core team members, library developers, and other highly experienced Clojurists often jump in to answer questions. As a maintainer of some Clojure projects, I try to follow their example and give back.
I started playing around with Clojure back in 2014 by implementing a rudimentary polyglot persistent news feed microservice. That microservice used Ring which sits on top of Jetty. I blogged about that implementation at https://glennengstrand.info/software/architecture/oss/clojur... which is my personal blog. Last year, I re-evaluated Clojure with a feature identical microservice. This time, I integrated with Donkey which sits on top of Vert.x and https://glennengstrand.info/software/architecture/microservi... is where I blogged about that implementation.
I put each of these implementations in a load test lab where I collect then analyze the performance data for comparison purposes. Those two blogs include the performance results. It is hard for Clojure to compete with other tech stacks, primarily because each method call in Clojure goes through Java reflection which is pretty slow. There is a way to prevent the use of Java reflection in Clojure but then the Clojure code doesn't end up looking very Lisp like. The usual advise is to use type hints very sparingly like in hot spots in the code.
Clojure absolutely does not reflect on every call, and no it does require type hinting every argument to every function to get that speed. Especially after Clojure 1.8 came out, huge performance gains were had by inlining far more direct function calls. You can achieve near Java performance on most code, especially if you are okay sacrificing the immutable data structures and lazy sequences. I think this is what makes Clojure shine: you get a beautiful, functional, immutable, easy to understand world, and mostly it is extremely performant, and if it's not, you can have a few functions wrapping uglier but more performant bits, and Java interop is also extremely easy if you want to drop another level down. All the Java tooling for analyzing stack traces and bottlenecks works pretty well if you need to chase performance issues too. And my god it's going to be a lot faster than a lot of Ruby and Python etc apps. I say that with love as long term Rails programmer.
You do still need to use type hints to achieve best performance, especially in numerical code. That said, even if I’m doing primitive math and using arrays in Clojure for performance sensitive code, it’s still a nicer experience than other JVM languages for me.
> It is hard for Clojure to compete with other tech stacks, primarily because each method call in Clojure goes through Java reflection which is pretty slow.
That's not true. Clojure functions calling each other do not use reflection. It is only when interoping with Java, and trying to call a Java member method of an object that reflection might be used if there are multiple concrete implementation of the method and the compiler can't find the right one at compile time. In which case, you can provide a type hint to tell it which one you want.
Additionally, you can use Leiningen's `check` to quickly identify all the places that need hints. It's not something you need to keep in mind while you're working.
You can take a look at this blog post to learn more about reflection calls in Clojure. There are other posts in that blog that goes deep into Clojure high performance.
Most data focused microservices for business applications devote most of their code to calling databases. The PoC microservices that I implemented in order to evaluate Clojure use Java libraries to communicate with Cassandra, MySql, Redis, and Elastic Search. Clojure is not currently at a point in adoption where it makes a lot of sense for these database vendors to support native Clojure libraries.
While it is true that not every method calls goes through Java reflection, it is also safe to say that every API call to this kind of service will end up making a lot of calls via Java reflection unless you use types hints all over the place in the DAOs.
The reflection lookup takes about 0.04 msecs extra on my laptop.
When you're doing things like I/O, an extra 0.04 milliseconds won't matter much, since the operation is already a lot slower then that. So hinting in those case generally isn't really needed, as it won't cause visible performance gains in your application.
Where hinting is worth it, is in tight loops where 0.04 ms will add up. Numeric code that uses arrays especially benefits, since the benefits of the array are lost to the reflection overhead otherwise.
Your claim, that the latency cost of reflection is small in comparison to the latency of performing I/O, seems reasonable assuming that the service is processing only one request at a time. That is not the case for typical microservice architectures and certainly not what is happening in the load test by which each of these service implementations are compared.
There is an upper limit of requests to which any service can handle concurrently. Usually, that limit is based on memory but there could be other factors too. After that upper limit is reached, additional requests wait in a queue or get rejected. From Little's Law, we know that the number of requests in any system is the product of the rate of ingress and the average time it takes to service a request. Since that number of requests is bounded, even small increases in the average process time can have a significant negative impact on the ingress rate that the service can sustainable handle. If you are not familiar with queuing theory, then perhaps you have heard of onlooker delay being the major cause of traffic jams.
Is running Clojure services with Java reflection the end of the world? Of course not. Just be aware that it does make performance for those kind of services look not quite as efficient when compared with services that do not use Java reflection.
For sure. YourKit is an excellent piece of software to discover the hot spots in the code that are the bottleneck in such cases. It takes just a few minutes to discover and add the type hints in the proper places.
Not sure I "get" Clojure yet, but I appreciate the effort and will poke at it further.
One piece of feedback - "Range of N" should clarify that "range" is generating N numbers starting at 0, so N won't actually be included ("0 to N exclusive"). I know this is fairly common for most programming languages, but "So (range 5) will return numbers from 0 to 5." is still ambiguous enough that I think it should be clarified.
Also can't copy the text from the instructions or REPL (at least in Vivaldi on Windows 10).
edit: I'm not a huge fan of the "multiples of 11" algorithm ("100" and "11" never actually show up in the expression...), so I ended up with this abomination which I think strongly reiterates that I don't get Clojure yet...:
The conditional is probably not needed, but I was curious how they worked... Also glad that range accepts a float for the second value, as I forgot to wrap that in "int" (and "Math/round" didn't seem to work). And yeah, tested and this works just as well for at least the basic version:
Thanks. I guess the docs are correct about "end" defaulting to infinity, as "(range)" by itself in the REPL just locked everything up (for about 2 minutes, then resulted in an error of "Invalid string length")
I would love to work with clojure. I thought there were opportunities for to work on a clojure project in my previous company. But I was told that no one likes to work on these products hence why they moving away from clojure and rewriting everything in java or scala.
I wish I could tell you exactly why they hated it, I never got an answer.
Speaking to some of the individuals, they thought it came down to tooling, no career benefits and some people really missed types.
The tooling is actually great, but kind of hard to discover if you're a beginner. The clojure.org website isn't very beginner-friendly, unfortunately.
Types... I find most people missing static type checking are really reliant on a certain way of programming which isn't applicable to Clojure, e.g. write code, look for red squiggly lines, fix type signatures, compile, wait, fix the bugs the compiler tells you about.
Clojure is much more exploratory in the way that you are always connected to a live system (like other Lisps) plus most of your functions are small and pure, making their logic self-contained and simple to deal with in isolation.
That mirrors some of my experience of Java vs a repl heavy Python habit, BUT — this misses the experience of diving into and reading unfamiliar or new codebases. For me, this is where the chunky type system and regularity of even the worst maintained Java codebases really shines.
> Types... I find most people missing static type checking are really reliant on a certain way of programming which isn't applicable to Clojure, e.g. write code, look for red squiggly lines, fix type signatures, compile, wait, fix the bugs the compiler tells you about.
I use a repl in Haskell just as much as I used a repl in Clojure.
The development experience is also somewhat similar if we're talking in terms of what you described here. In Clojure, you also fix the bugs the compiler tells you about, except the compiler in this case is one you partially implement yourself with a test suite.
> plus most of your functions are small and pure, making their logic self-contained and simple to deal with in isolation.
It's the latter argument that did it for me. I never again want to work primarily with a technology that doesn't have encapsulation of optional types. It's just such an uninteresting problem to have to continuously suffer from.
Huh, good post, thanks. What are you usually working with, Haskell? Or I saw Elm mentioned on your blog. I've been considering learning Clojure as something new to help broaden my skills (currently using TypeScript in my day-to-day and some noob-level knowledge of Haskell). Your post has me reconsidering, haha >_>
Yes, I mostly work with Haskell. I'm the CTO at Supercede and our project is currently ~100,000 lines of Haskell code.
I think every developer ought to at least learn Elm. Being forced to think so lucidly about the types and effects of your systems is unreasonably effective, and I think it shapes the way you then write code in other languages.
slightly offtopic: My company (Meta Blocks), and my last company (Status) are hiring for Clojure roles. Please reach out to me if you are considering a switch.
You could then pair it with a pastbin and share/run code snippets (with some URL shortener). Could you imagine the possibilities for bug reports for instance? :D You could reproducibly demo bugs (well and features as well)
When it comes to specifically dynamically loading external libraries - is there a reason SCI can't incorporate `add-libs`? I mean clearly some libraries wouldn't load and will break (like if it's using Java code or something), but I imagine it could work for a large subset of code.
I get there are a few ways to have a REPL in the browser, but it seems a bit primitive if you can't get any libraries in (short of copy pasting namespaces). I understand babashka includes some internally - so it makes it at least suitable for a subset of tasks. But I think this dynamic library loader is a more flexible solution (or I'm overlooking some technical hurdle here)
I thought it was in effect fetching remote code, from git or wherever, and then executing it (or loading it) in the local environment.
I'm just curious to understand the situation better, but which part architecturally strictly needs the JVM? Even with babashka, or on this website, I don't think anything stops me from Frankensteining in an external library in a running REPL. I could technically fetch the content of a library's repo and then copy each file and run its contents (with the ns blocks) in the REPL. Then the library would become locally available (assuming it's a very plain Clojure library that is)
I assume `add-libs` is sort of a convenience wrapper for the same idea
I thought you were referring to https://insideclojure.org/2018/05/04/add-lib/ which is an experimental feature of tools.deps.alpha. But sure, there could be an (add-lib ...) function in this environment which loads some external code.
rereading the link I guess there are some implementation specifics that are JVM. I'm not really familiar with CLJS to say how those translate - but in principle is sounds like it could work on both platforms
add-libs lets you add Java libraries as well, or Clojure libs that themselves depends on Java libs. So for the JVM you need a JVM specific implementation that can pull Java libs. And when adding pure Clojure libs, you're right, it could be from source directly similar to typing it all into the REPL.
For JS, it's a bit more complicated for the Clojure side, because the compiler isn't in the runtime, SCI is interpreting the Clojure code, it is not compiling it to JS and loading it, which makes adding a source pure Clojure lib tricky. But it could possibly be done for compiled Clojure libs and JS libs, that said I think you couldn't use any forms of advance compilation, because that messes up the references. So there's a lot of caveat I think to doing it in JS and that's why it's not done.
Oh okay, I see the distinction. The library would be interpreted and not compiled to JS - and I guess there would be a needless performance penalty. So it wouldn't be of much use outside of this particular scenario.
Nonetheless it seems like something that'd be quite handy for bugreports and sharing code :) One thing I've noticed is that there is friction with writing minimal bugreports/demos/examples. It usually involves making a new repo with a `deps.edn`. Then you gotta push that to github and send a link.. It's all very doable but there is overhead and people don't want to have one off demo repos scattered everywhere for every issues they've responded to on Github. And the people on the other side of the equation don't really wanna pull and execute your mini-repos either. `add-libs` already lets you boil it down to a single file which is great, but having it all in an executable pastebin or forum comment block would mean everyone is seeming the same environment/configuration, same dependencies, same code and the same output.
That said, looking over a few of my projects I do have to admit that pure Clojure dependencies are the minority :)
Dependencies with their own JS or Java dependencies sounds more hairy.. I wasn't even thinking in that direction. I'm sure that's part of why add-libs is still a work in progress
IT's worth knowing that this is you typing into a REPL, that when you are actually doign REPL driven development you almost never type directly into the REPL in Clojure.
ALmost all editors I've seen allows you to send code to the REPL from your editor, and get the results in line in your editor.
> How do people who write Clojure deal with lack of type checking?
The short answer is that you don't. Rich Hickey, the creator of clojure, made the language *very* opinionated by design. And one of the strong options is that folks should be aiming for simplicity in their code, checking their code as they go along using the repl in real time. Dynamic typing makes it much easier to do this.
Whether or not this is a good approach (I happen to like it) is up for debate (and likely a matter of taste too), however that's at least some of the rationale at a high-level.
To really enjoy using clojure, you have to do things the way the language wants you to. If you try to bring your java style workflow into clojure, it's not going to be a lot of fun.
I've found "cider" for emacs to be a really good clojure IDE and repl. The workflow is a bit different from Java - I find that I am looking up documentation within my editor more often (which explains the arguments and return values), and then trying out little snippets locally using the extremely powerful repl. You slowly build up your program from smaller programs that you have tested to work, so by the time you run the whole thing, you only have a couple typos/issues.
I think the other key to working with clojure is to set up your program as a set of transforms on simple data structures as much as possible such that complex class interactions aren't used much, and that cuts down on the need for great intellisense and static typing. Ymmv, of course, but I've found this way of working more productive than when I was working in Java.
You can get autocomplete for Java methods in Cider (Emacs) and probably Cursive (Intellij) afaik. Type checking in intro code can be a bit difficult, but REPL/live development really helps. You are going to catch mismatched type at first call, so you can usually get there by reading the Javadoc, but it does take some practice. Most Clojure code also works with Java through the smallest interface possible at the edge of your system, so it is very rare to have to deal with those issues in your "main" code.
Depending on your IDE, you could use different tools to provide the intellisense-like functionality for clojure. For Jetbrains, there is Cursive, for emacs, Cider, etc. I won't disagree that the beginning experience can be lacking even when coming from Java - for instance a good coding environment also includes setting up a hosted repl, but the extra effort does pay off in being able to code interactively as your program runs.
The lack of type checking is due to the entirely different paradigm that Clojure is built around. There are many benefits to it, but I can understand the frustration if you expect to use the language in a way that goes against that, especially when dealing with the interop layers. The fact that it is built upon the JVM is powerful but it's not necessarily an easy language switching from a language like Java.
> How do people who write Clojure deal with lack of type checking
If you've worked in Python, Ruby or JS it's pretty similar.
What I do is I use variable names that make it more obvious what type things are. I also make use of destructuring and Clojure Spec to indicate what keys a map has or what values in a tuple are supposed to be.
Also the REPL can help you quickly explore the state and functions which you can use to try and inspect the types, that's useful when trying to understand someone else's code base which might not have had the most readable code.
And since you do REPL driven development, as I code I run the code constantly in the REPL which will throw type errors when I make one. It has the bonus of catching logical errors as well as helps me figure out how to implement what I want more quickly.
> And auto-complete when it comes to Java library interop
You should get some auto-complete here depending on your tooling. It should lost all possible methods of an object, just not constrained to the direct type. For me that's often enough as I know kinda what I'm looking for, so I can find it and auto-complete.
If you want the auto-complete to a specific type, you can type hint the object and then the auto-complete will list only methods of that type.
> How do people who write Clojure deal with lack of type checking?
Runtime asserts, typically.
It ends up being somewhat less of an issue in practice than someone coming from Java or Kotlin would assume, I think, because clojure really only has a single datatype: a sprawling, immutable soup of nested maps and vectors. The design of the standard library is such that your standard data manipulation functions will basically always work on every data structure you get passed, so you end up designing your internal APIs so that they take in a blob of data, perform an operation if the soup has the right components (and probably throw an exception if not), and then spit out that changed blob of data.
Is this better (or at minimum no worse) than having a type system? That's a broader question of philosophy and taste that I'm still undecided myself (for instance, I think the type systems in Haskell and Rust are really quite valuable, but the ones in Java and Go don't really pull their weight), but it's not something I really miss on a day-to-day basis writing clojure code--it's just a different way of doing things.
> And auto-complete when it comes to Java library interop?
My experience as both a professional and hobbyist clojure user is that this is not a huge issue in practice:
- Java interop tends to get wrapped in clojure defns so you can get your auto-complete pop-up off the namespace alias.
- At least with my setup (emacs+cider), adding a (:import (...)) clause to a ns form allows me to autocomplete on all method names of classes in scope (and provides type-annotated signatures), so I can still auto-complete when performing interop. I believe IntelliJ+cursive is even better about that sort of thing, and if I were working directly with Java libraries/APIs a lot I would probably consider switching IDEs for that reason.
For intellisense / auto-complete, that is just down to your IDE.
See here, I have autocomplete and intellisense using VSCode and Calva, but I can also hover a function and see the clojure docs for it, then copy them to my IDE, and run the example code all within my editor. Then remove it when I'm done playing. The doc examples go into a rich comment form.
Thanks for this! I just finished the tutorial and really enjoyed it! Found myself hoping for more. Maybe a small series that would teach one the necessary basics of Clojure so one could actually do a real project in it afterwards. That would be awesome :)
You’re leaving a lot of value on the table if you don’t make use of Java libraries (even the standard ones). Many open source projects are thin layers over Java bindings, and it’s a huge free buffet lunch if you can do that yourself. But as long as you know the interop syntax you can pick that stuff up as and when you need to. Many people get by googling some JVM command line parameters when they hit a problem (running out of heap space, bringing in additional modules etc) and there are still plenty of people who work in pure Clojure and claim never to have to think about Java or the JVM, even the Maven bits.
Java the language? Very likely not. As proof - a majority of Clojure runs in JavaScript without any code changes (via ClojureScript). That said exceptions tend to look like Java classes, so you’ll definitely see Java bits.
Java the JVM? You _probably_ don’t need to know it to get started, but you’ll have to get familiar with JVM deployment, dependency management, packaging, JVM memory settings, etc if you want to run something outside your dev machine. Getting to 90% here is pretty easy, but the last 10% (to me at least) tends to feel a lot more difficult that other languages.
As a small pro-tip: use jstack. I wish every language came with a jstack. It just dumps the stack traces of all threads of a program by pid.
My experience with ClojureScript is you absolutely have to know the host environment because you will encounter type errors and the maintainers have negative interest in making them clear to users (as in flatly refused offers to contribute changes to make those errors easier to understand).
There is nothing worse than library or language maintainers who do not understand that "pure" error messages are absolute hell. If 99.99 % of the time a user should simply write x instead of y, the message should absolutely state that if possible.
Non-trivial I/O requires knowledge of the host. Trivial I/O is things handled by the slurp or println functions. The obvious way of doing GUIs is swing (Java), 3D means JOGL (Java), file manipulation through clojure.java.io expects a grasp of Java's Reader/Writer abstractions and associated structures.
It doesn't require Java as such - I can't really program in Java and Clojure is great. A lack of Java fundamentals is still a handicap and probably around my #3 annoyance as a Clojure user. It makes dependency management (#2 annoyance) harder because the already complicated Java dependency management systems gets a thick layer of Clojure confusion ladled on top. The stacktraces obviously take the spot for #1 annoyance, which is a consequence of the Java but knowing Java won't help navigate them.
Can't recommend Clojure enough though. Very fun language.
Mostly only for interop, which unfortunately includes the language itself. Normal usage you shouldn’t have to care. But if you get internal stack traces you’re gonna be learning about the various Map implementations etc, all of which are defined in Java. Significantly easier to trace than machine code though!
Mostly for understanding any stack traces you might see while running the code, I'd say. Other than that, you can use the bulk of the core language without needing a deep level of Java knowledge. Those bits of Java you may touch can be used as 'incantations' until you learn more.
There are some things that you eventually want to learn. One of them is the underlying data types. For example if you use Clojure on the JVM you are dealing with Java strings, if you use a JavaScript runtime with ClojureScript, then you get JavaScript strings.
The tutorial showcased here runs on ClojureScript for example.
So:
(reverse "abc")
Becomes:
("c" "b" "a")
In ClojureScript. But in Clojure it becomes:
(\c \b \a)
Since Java has a character type but JavaScript doesn't.
It's little things like this that. The benefit here is superb interop, but in turn you aren't as isolated from the host language as you would be in another language.
Not right away no, but it will help to have an understanding of Java/JVM for Clojure and Browser/Javascript for Clojurescript if you are intending on building real systems.
For some feedback, you might consider using Let's Encrypt for setting up free TLS certs, or hosting on Github Pages to get it automatically. With just HTTP, my browser (and many others) will show a scary warning telling me not to use the site.