The Jank Language: LLVM Hosted Clojure
jank-lang.org
jank-lang.org
It states that it's not very buildable i.e. that the build process is very fragile. Building it with nix definitely works, I just tried it.
> jank is under heavy development. It's safest to assume that any feature advertised is partially developed or in the planning stages. There is no sales pitch here; just a lot of work and some big plans. All development happens on Github, so watch the repo there!
not "jank aims to be..." No, "jank IS"
this is... ridiculously uncool and misleading.
The usage of "jank" here refers to its design; the concept of jank. This is different from its current implementation. jank is designed to be many things, as the landing page shows, and the implementation is catching up with that.
Nobody cares about the "design" of a language when there is no implementation that fully realizes the design. Otherwise, anyone can say "my language is 100% compatible with clojure, but oh, yeah, the clojure compatibility part isn't fully implemented yet".
If your landing page said "jank's goal is to be 100% compatible with clojure", or "jank is designed to be 100% compatible with clojure (when fully implemented)" that would be fine. Right now the statement is just false, because right now, the language isn't 100% compatible with clojure.
I really think you should change the wording -- and I say that as someone genuinely interested in the project. I hope you achieve 100% clojure compatibility, that would be really cool. With the way things are worded now the first impression you are giving people is that the language implementation is way more complete than it is, which I believe will be a big negative for you in the long run.
Like clang is still C, just like GCC is still C
During AOT compilation, though, jank does whole program analysis and uses the type information to box/unbox, monomorphize, compute at compile-time, etc. I'll be publishing documentation on each of these designs (the type system, the JIT, the interop support) on the website as things move forward. Stay tuned. :)
It also contains a cached heap (in fact most of it is cached heap) which is one reason these binaries start as fast as C programs do. Especially important for Clojure where the runtime and libraries are written in such a way as to do lots of unnecessary work at startup. Native images can cache all that so you get rid of the startup overhead of Clojure itself.
I'm almost sure the author would drop the project as soon as he realizes and lives the frustration of not being able to come anywhere near close to the performance of Clojure in the JVM.
Rust is competing with C++ for writing low level systems, whereas Clojure is designed for writing business applications.
Low level systems tend to have stable and well understood requirements, whereas the same cannot be said for business applications. Efficiency is not a primary concern of business applications, but flexibility is. The efficiency of Clojure is often "good enough" for business application, and there is no peer in flexibility. This explains the many fintech companies using Clojure, as well as many other B2B companies.
Also, Rust binaries have been of comparable size in the past. Check out this question where someone asks why Hello World is 3mb even in release mode:
https://stackoverflow.com/questions/29008127/why-are-rust-ex...
This 3mb doesn't have any cached heap, doesn't have any garbage collector, doesn't have support for high quality exceptions and doesn't have much in the way of a cross platform abstraction, whereas the Clojure-as-native binary has all of those and more.
There's a giant pile of things you can do here to try and reduce Rust's binary size:
https://github.com/johnthagen/min-sized-rust
... but as you can see, it's a lot of work.
Rust gets great press and HN is flooded with "let's do it in Rust" type comments. But I think sometimes we need a reality check. Rust is an extremely different language to most others, even C. The combination of the borrow checker+everything async is not obviously better than everything else, not even in the low level domains even though Rust is deservedly picking up loyal users there. The moment you care about development speed i.e. for the sort of apps Clojure was designed for, you're going to be better off with a GC and a strong runtime that abstracts the platform well.
Is it really 100 percent compatible with Clojure or is that just aspirational.
Also, is it the case that lots of libraries/workflows outside the core language in Clojure assume Java integration? I always thought a big Clojure selling point was the awesomeness of Lisp while having full access to the libraries of Java.
Aspirational. The project is really early still, and in the middle of a grand rewrite so basically it doesn't build at the moment, see https://github.com/jeaye/jank/issues/7
Current progress seems to be outlined here: https://jank-lang.org/progress/
For example ClojureScript has all the “wat” semantics from JavaScript primitive types.
You can build libraries that try to hide the differences but the leaky abstraction is inherently still there.
That seems a shame - a language implementation is an abstraction that doesn't have to leak, the layer below can be totally inaccessible if desired.
Basic Java interop is pretty simple (and concise!). But unless you're specifically needing a Java library, you won't even do this much. You _can_, but it tends to be more for performance optimization in my experience.
In other words, don't be scared of JVM. I would say it's kind of like Elixir/Erlang. You can do real things with Elixir without knowing hardly anything about Erlang. Of course, later on you can do more awesome things if you know how and when to leverage the Erlang VM. Same probably goes for Clojure/JVM.
Still, it's a fascinating project, which I will watch closely.
Why would this be important? Well because they are relying on LLVM for optimization, and while that's good if you know type info (via annotations or lower tiered profiling), there's a lot of code to start using this and those optimizations can be fragile.
If you read the articles posted here in the past year on the Erlang JIT they mirrored this sentiment, earlier "advanced" JIT attempts built on classic static language techniques like LLVM prefers failed because they didn't have enough type information, whilst the "simple" JIT that mainly removed overheads and concerned more with local types was showing good promise without too much engineering headache.
Very much inspired by Clojure, with a lot of time put into benchmarking and profiling.
It's too early for me to share numbers on jank vs Clojure itself, especially due to the rewrite, but my earlier jank versions were definitely competitive with AOT compiled Clojure jars.
https://github.com/clasp-developers/clasp/
> Clasp is a new Common Lisp implementation that seamlessly interoperates with C++ libraries and programs using LLVM for compilation to native code. This allows Clasp to take advantage of a vast array of preexisting libraries and programs, such as out of the scientific computing ecosystem. Embedding them in a Common Lisp environment allows you to make use of rapid prototyping, incremental development, and other capabilities that make it a powerful language.
Not sure interop with C/C++ would be somehow easier.
They are basically trying to make my new dream language.
The only thing I am worried about is compile time. It seems many projects using LLVM have trouble with that. See Julia with its LLVM JIT, though they did manage to improve it somewhat. I guess it is simply to early to tell, lets hope for the best.
I’m very interested in runtime performance as well, though, and how it compares to vanilla JVM-based Clojure.
The reason julia has so much compiler latency is not that we have a slow compiler, it’s that we compile a LOT of code.
Julia is very very (runtime) performance oriented, and so we do everything we can to speed up execution time. This means that we have very aggressive code specialization, inlining, and unrolling heuristics, and people take heavy advantage of them. It’s a very normal thing in julia to try and write very high level generic code that performs like low level optimized code by taking advantage of versions code generation techniques.
In addition to all of that, we end up recompiling a lot of code that was already compiled before because we have very strict heuristics to make sure that when a method body is altered, all downstream dependants of that method get invalidated and must be recompiled, and sometimes the heuristics overeager.
Julia compile times have been dropping rapidly over the past couple years mostly because people have put a lot of work into avoiding invalidations, and being smarter about how compile-time code generation is approached. Almost none of that improvement has to do with LLVM, it all happens well before we hand things off to LLVM.
It does look like it has some technical similarities to Julia, with the LLVM JIT and such. It'd be great to see some benchmarks.
Also see the GitHub[0], which looks like the author put a ton of work into this so far. Very impressive.
That said, looking at the progress page[1], this is still far from achieving 100% feature parity. I'll be following it closely with my fingers crossed though.
If you want to support this, it looks like the author is also accepting GitHub sponsorships[2].
[0]: https://github.com/jeaye/jank
However the issue is that one of clojure's strengths is the JVM, so I'm interested in how this plays out
"Reference counting is the current design. This is something I'll be digging more into, but I really don't think a GC would be ideal."
Internally this is done with structural sharing, so it's way faster than copy on write, but it's still potentially allocating a half dozen objects for what would usually be a trivial operation that does not involve allocation.
On the plus side, Clojure in theory has some pretty nice guarantees that older objects can't point to newer objects. From memory Haskell takes advantage of these guarantees to accelerate its version of eden. There may be a way to get a fast GC that's not nearly as complex as the full JVM GC.
> The only reason Clojure is viable for production use is because it relies on the really good JVM GCs. How will Jank try to solve the the GC problem?
I think this original statement is wrong because people are fine with poor performance (see Python, Ruby) if the language (ecosystem) is effective. And Clojure is effective (IMO) moreso because of the JVM ecosystem not the JVM's performance.
"Jank" describes undesirable, problematic, glitchy UX. I genuinely thought that the title was denigrating Clojure on LLVM. Maybe it's intended to be funny or ironic, and if it catches on it won't matter, but it sure seems like a footgun.
While this is LLVM JIT, so will compile the Clojure code to native code just-in-time, and then run the native code.
Because is is AOT compiled is starts fast, but its much slower than JVM-Clojure for long running tasks.
A natively compilable Clojure would be great for other use cases, especially if it can be made more memory-efficient (Clojure is a memory hog)
I do feel like this needs to be explicit in the license, especially if jank includes a runtime that is distributed along with your programs.
Looks like a great project to follow!
Your comment here is at odds with my understanding of how the Sleepycat license works and how Berkeley DB is licensed.
For your thinking about Clasp and alternatives for Common Lisp: I don’t think there is a gap to be filled with Common Lisp deployment. For free, build SBCL from source enabling heap saves compression. Then creating SBCL Common Lisp apps produces a fairly small app size, easy to build and distribute. Or, pay for something like LispWorks and creating compact standalone applications is perhaps even easier.
> Where jank differs from Clojure is that its host is C++ on top of an LLVM-based JIT.
So this is similar in kind to pypy? (or maybe hy on pypy..).