Hey folks. I'm the creator of jank. I didn't expect to be on HN today, but I appreciate the interest.
In short, jank is Clojure, but it's on LLVM and has seamless C++ interop. You still get full nREPL capabilities, can redefine anything on the fly, and we can actually JIT compile C++ code alongside your Clojure. The seamless C++ interop is first of its kind, for a lisp, and is done by JIT compiling C++ alongside the LLVM IR we generate for jank and then stitching them together into one IR module.
Note, jank isn't released yet. I'm targeting the end of this year for the first alpha release. I put out monthly development updates on the jank blog, with the next one coming out this week.
I don't have anything terribly insightful to say other than that I really like this project. I loved using Clojure back during it's brief time in the sun, it was a great time, and jank has all the same awesome vibes. I haven't used it on anything serious yet, it seems like it would be great for like a `zig`-flavored approach to C++ builds for example. I'm doing a big C++ project ground up at the moment, I think I'm going to get jank into it and see if I can automate some stuff.
The compilation strategy is very much what Carmack did in Trinity and whether you got your inspiration there or independently had a great idea, that's good company to be keeping.
beautiful work. clojure is very nice. one of the most impactful talks I have ever seen was from Rich Hickey - simple made easy.
however my only gripe with clojure while it's easy to write and comprehend at first - it's difficult to read. & yet most our time we read code not write it. but then again it might be my lack of brain power.
I agree with you, but perhaps in my own way. Jumping into an arbitrary Clojure program can be tough, since the data shapes may not be defined anywhere. Hopefully the program uses spec or malli, but even then, unless they annotate every function with the shape it expects, you may be left needing to REPL in and poke around. However, REPLing in to check just a function or two may not be easy if the program requires some setup and doesn't use integrant or similar.
Once Clojure parity is achieved, I'm interested in static typing, pattern matching, value-based errors, and some other opt-in improvements that I think will greatly improve both readability and toolability (i.e. how well tooling can work with the code, based on what it knows of the code). Stay tuned. :)
If you can run it, then you can REPL it, no matter how deeply nested. Scope-capture (https://github.com/vvvvalvalval/scope-capture) has been probably the most important tool in my box. Hope jank supports it eventually.
Thanks for jank! It’s great to be reading about it, listening to you talking about it at conferences, and I can’t wait to try it out!
What’s your take on Hickey’s talk titled “Maybe Not” which fundamentally criticizes static types? Is there a middle ground where some form of static typing makes sense in a Clojure-esque world?
Heh. Hickey once debated with me at length about visual neuroscience, a subject I have a master's degree in and he doesn't. At no point did this stop him from confidently asserting things.
I have to wonder if "Maybe Not" is similar, since he's not actually an expert in types, either afaik.
I'd personally say typing or not is a style choice, but your criticism here seems to be that Hickey doesn't have a visual neuroscience Master's degree which seems a bit arbitrary.
If your argument is you are an expert but Hickey is not, criticising him on his language design skills seems like a logical mistake. He's one of the foremost language designers of the current era. "Maybe Not" is a speech by an expert talking in his field of expertise.
If your argument is that his confidence is unfounded, again, he's an expert talking in his area. He can reasonably take a confident attitude in that, even if he has unfounded confidence in other fields he isn't an expert in. Lots of experts do that, it is a well founded stereotype of smart people.
He doesn't need to be an expert in implementing types to judge whether they are a good language feature.
Being an expert in type theory or language design is not particularly relevant either. The most relevant expert would be the lowly maintenance developer.
Rich has many great ideas and he founded Clojure. I respect him deeply. On typing, however, we do not agree entirely.
For a practical example of a Clojure-like language with a completely static type system (with affine typing), see Carp. https://github.com/carp-lang/Carp
I don't see why there can't be a Carp mode in jank, with bridges in place to connect the Clojurey world from the Carpy world. This would allow jank users to develop interactively to start with, figure out their shapes, use the REPL, etc. Then, if they want, they can lock down some parts of the code for both performance and correctness gains.
Been a while since I've watched/read it, but I remember the ideas in Maybe Not being quite interesting.
To me, the really important idea wasn't a criticism of static types in general.
Instead it was the idea that static typing in most (all?) mainstream implementations conflates concepts that should be separate, specifically the shape of the information that we have (e.g. what fields of what types), and whether a particular bit of information is available and required (e.g. nullability).
He contends that the former belongs in our usual "type definition", whereas the latter relates instead to a given context. For example, my PassportForm type always has a date-of-birth field in its _shape_, but whether it's statically required/present to exist depends on whether we're at a HTTP API boundary, an internal domain function boundary, writing into a database.
It sounded like that kind of "nullability masking" was intended as a feature of Spec, but I don't get the impression it was ever implemented.
I don’t think Rich was criticizing static types as much as saying that they aren’t giving you as much benefit as you think they are and that they complicate program evolution over time.
They might not give much execution benefit and they may indeed complicate program evolution, but they DO aid readability, document-ability and refactoring!
My comment to code ratio is magnitudes higher in Clojure than in other languages, which helps a lot with this.
Also writing Clojure can be incredibly terse, resulting in quite high-effort when reading. Conversely, a lot of time I can condense hundreds of lines of equivalent python into 5 or 6 lines of Clojure. Having all of this functionality condensed into something you can fit in a tweet really helps for grokking larger parts of the dataflow or even larger system. So there are tradeoffs
Plus structural editing and the repl really help with the “reading” experience (reading in quotes because it’s much more interactive than reading)
In my (limited) experience with Clojure and other functional languages, this is usually true under situations where:
1. You’re mapping or reducing some dataset
2. Your iteration logic does not branch a lot
3. You can express your transformation logic using higher order functions (e.g. mapping a reduction operation across a multidimensional array)
Some domains have a log of this style of work—finance comes to mind—others do not. I suspect this is why I’ve personally seen a lot more of Clojure in finance circles than I have in other industries.
Maybe hyperbole on the frequency, but not the condensation. I meant more along the lines of “most of the complicated code I write in Clojure is an order of magnitude more dense.” _Most_ of the code I write would be 1:1 or 1:2 with other languages, it I don’t think it’s the type of code OP was referring to.
The 1:20+ is definitely not hyperbole though. Using transducers to stream lazy reductions of nested sequences; using case, cond-> and condp->; anywhere where you can lean on the clojure.core library. I don’t know how to give specific examples without giving a whole blog post of context, but 4 or 5 examples from the past year spring to mind.
It’s also often the case that optimizing my clojure code results in a significant reduction of lines of code, whereas optimizing Python code always resulted in an explosion of LoC
Personally I find Python particularly egregious. No map/filter/reduce, black formatting, no safe nested property access. File length was genuinely one of the reasons I stopped using it. The ratio would not be so high with some languages, ie JavaScript
Even with Elixir though, many solutions require 5-10 times the amount of lines for the same thing thing in Clojure. I just converted two functions yesterday that were 6 & 12 lines respectively in Clojure, and they are both 2 pages in Elixir (and would have been much longer in Python)
I find 95% Clojure has the right tools to write very terse code. But in some cases the functional transducer/piped paradigm can't be contorted to the problem.
Usually these are problems where you need to run along a list and check neighboring elements. You can use amap or map-indexed but it's just not ergonomic or Clojure-y (vs for instance the imperative C++ iterator model)
The best short example I can think of is Fibbonacci
Personally, I would normally reach for loop to check neighboring elements very ergonomically.
(loop [[a b c & more] coll] (recur (apply list b c more)))
There’s also partition if you're working with transducers/threads/list comprehension
(partition 3 1 coll)
Or if you need to apply more complicated transformations to the neighbors/cycle the neighbors
(->> coll cycle rest (map xform) (map f coll))
Using map-indexed to look up related indices is something I don’t think I do anywhere in my codebase. Agreed that it’s not ergonomic
EDIT: those Fibonacci functions are insane, even I don’t understand most of them. They’re far from the Clojure I would advocate for, most likely written for funsies with a very specific technical constraint in mind
Yeah, I guess partition to me always looks like a dangerous tool - for instance I have a sequence of numbers and I want to do a 5 point rolling average
You could do `(partition 5 1 coll)` and then average each element in the resulting seq.. It's very easy to reason about. But I'm guessing the performance will be abysmal? You're getting a lazy list and each time you access a 5 neighbor set.. you're rerunning down you coll building the 5 unit subsets? Maybe if you start with an Array type it'll be okay, but you're always coercing to seq and to me it's hard
Taking the first 5 elements, recurring on a list with the top element dropped is probably better, but I find the code hard to read. Maybe it's a familiarity issue..
Yeah like I said I reach for loop first and foremost. This is what it would look like with comments if it were actually something complicated (although the comments are quite trivial here):
(loop [coll [1 2 3 4 5 6 7 8 9] memo []]
(if (< (count coll) 5) memo ;; Stop once there are less than 5 items
(->> (take 5 coll) ;; Take the first 5 days
(reduce +) ;; Sum them
(* 0.20) ;; Divide by 5
(conj memo) ;; Append to the end of the memo
(recur (rest coll)) ;; Recur for remaining elements
,,,))))
Realistically if performance was a consideration I would probably do:
(loop [[a b c d e :as coll] [1 2 3 4 5 6 7 8 9] memo []]
(if-not e memo
(recur (rest coll) (conj memo (/ (+ a b c d e) 5))))))
Should be ~15 times faster to avoid the nested loop. If you want to change the min size it's still pretty clean:
(loop [[a b c d e :as coll] [1 2 3 4 5 6 7 8 9] memo []]
(if-not c memo
(recur (rest coll) (conj memo (/ (+ a b c d e)
(cond e 5 d 4 c 3))))))))
Exactly. I would use loop or partition+map/reduce for that case. I almost never use map-indexed. In fact I almost never use indexing at all. Mostly, when I have a sequential collection (vector, list, or generic seq), I need to iterate over all the elements, and I’m doing that with map or reduce. IMO, map-indexed has a code-smell that indicates that you’re reaching for an imperative algorithm when perhaps a functional algorithm would be better. Surely, there are times when map-indexed is just what you need, which is why it’s there, but typically not in my experience.
I would agree that map-indexed + *nth* is a code smell. I use map-indexed all the time though, just when I need the index, not other elements in the list
Maybe I'm just too used to Python (and I only know some Clojure) but I don't have the same experience. Usually using generators and itertools will really help you shorten your code. I'm working in a data science adjacent field so a lot of code is just frameworks anyways but I don't feel limited in pure Python either.
If you come across a post or an example that shows those differences, I would be very interested!
It was much longer prior to writing this comment (I originally used multiple arity helper functions), but it was only fair I tried my best to get the elixir version as concise as possible before sharing. Still 2x the lines of effective code, substantially more verbose imho, and required dedicated (minor) golfing to get it this far.
Replacing this report function (12 lines) + one other function (6 lines) + execution code (18 lines) is now spread across 3 modules in Elixir, each over 100 lines. It's not entirely apples to oranges, but trying to provide as much context as possible.
This is all just to say that the high effort in reading it is normally a result of information density, not complexity or syntax. There are real advantages to being able to see your entire problem space on a single page.
I guess it stands out. "Git" is similar. "Rust" isn't a very positive word either. Perhaps it's a new trend. Maybe the answer is "all the good names have been taken" and/or they are simply lazy.
Yep I won’t use anything with a negative self deprecating name like this. Because some tech bro will use it as a a basis to disqualify my entire resume or sabotage an interview after solving the leetcode trivia troll questions and whatever other video game battles they add to the interview process in the future.
Project manager fires the entire team except 1 intern to finish the project with 1000 points of stories in 1 sprint? Heh or did you just figure out jank wasn’t capable of doing the job what did you expect?
Hotfix to fix a bug with the stage environment because the SREs set it up wrong? No bro it’s jank it’s that jank thing. Source: ctrl F “jank” in the message analytics and copilot says all matches are in the stage environment and that jank is also a tech thing. It also bright up every engineers profile that lists jank as a skill. Time to pick a scape goat.
I’m a bit curious why you chose to implement this as a different language (even though it implements Clojure) instead of an alternative Clojure backend and/or C++ syntax extension.
Do you plan to make Windows support first-class? I think a lot of people looking at LLVM based languages are interested in alternatives to C++ for games.
> I’m a bit curious why you chose to implement this as a different language (even though it implements Clojure) instead of an alternative Clojure backend and/or C++ syntax extension.
jank is Clojure. However, the Clojure name is trademarked and using it requires permission which I don't have. Furthermore, I want to build upon the Clojure base to provide more, going forward. That may include static typing, value-based error handling, first class pattern matching, and so on. Those would be opt-in features on top of Clojure. All of these reasons lead me to not use Clojure in the name (like Clojure++, ClojureNative, etc).
> Do you plan to make Windows support first-class? I think a lot of people looking at LLVM based languages are interested in alternatives to C++ for games.
Indeed, a lot of game dev folks use Windows. Right now, jank's Windows support is limited. My initial audience is Clojure devs who want native access and lighter binaries. Once that launch has stabilized, I will focus on appealing to existing native devs who want to embed an interactive, functional language into their C++ applications. That will requires strengthening the Windows support, establishing stable native APIs, and writing the onboarding material for lisp, REPL-based editing, data-driven design, and so on. This is a much larger task, which is why I'm focusing on existing Clojure devs first.
I commented to this effect on Reddit, but my interest is entirely conditional on ability to embed Jank into a pre-existing C++ application as a shared library.
Ideally without controlling the code of the main application (e.g. to implement a plug-in).
Yep, this will be an important use case and will be officially supported. For the first alpha release this year, I'm focusing on Clojure devs, but support for existing native devs will come once things stabilize.
Hey, as someone who spent a few years reimplementing another language trying to decouple it from JVM (Scala JVM -> Scala Native), some pitfalls to avoid:
- Don't try to provide backwards compatible subset of JVM APIs. While this might seem tempting to support very important library X with just a bit of work, I'd rather see new APIs that are only possible with your language / runtime. Otherwise you might end up stuck in never-ending stream of requests to add one more JVM feature to get yet another library from the original JVM language running. Focus on providing your own unique APIs or bindings to native projects that might not be easy to do elsewhere.
- Don't implement your own GC, just use mmtk [1]. It takes a really long time to implement something competitive, and mmtk already has an extensible and pluggable GC design that gets some of the best performance available today [2] without much effort on your end.
- Don't underestimate complexity and importance of multi-threading and concurrency. Try to think of supporting some form of it early or you might get stuck single threaded world forever (see CPython). Maybe you don't do shared memory multi threading and then it could be quite easy to implement (as in erlang). No shared memory also means no shared heap, which makes GCs's life much easier.
- Don't spend too much time benchmarking and optimizing single threaded performance against JVM as performance baseline. If you don't have a compelling use case (usually due to unique libraries), the performance might not matter enough for users to migrate to your language. When you do optimize, I'd rather see fast startup, interactive environment (think V8), over slow startup but eventually efficient after super long warmup (like jvm).
I see that jank is already doing at least some of the things right based on the docs, so this message might be more of a dump of mistakes I've done previously in this space.
I love this project, and frankly I can't wait until I see Zig code stitched into and interoperating in a lisp via C transpilation, but I really do agree with the top commenter if you can't get Clojure trademark approval.
Anyways, keep up the amazing work, I wish I could have seen your janky talk at Strangeloop on another timeline.
My company had what was at the time one of the largest Slack enterprise contracts. You have no idea what internal corporate battles we had to face to get our higher-ups to take us seriously at every stage of adoption, and ultimately roll it out en masse. Slack succeeded in enterprise in spite of its name, not because of it. The actual product was phenomenal, relative to alternatives.
Yes, when you have the notoriety, distribution, and reputation-for-insults that Linus does, you can get away with things like that, because you're selling into a culture that already understands the "joke".
I worked for a while in a big traditional corporation. My team was a bit like a little enclave inside the larger organisation. They knew us because we had our top shirt buttons undone and wore brown shoes instead of black. When we interacted with the traditional suits the worst we got were chuckles and eye rolls as we said names like "Python", "GIMP" and "Cockroach" instead of the things they knew about like SAS and Oracle. We never met any resistance due to naming or anything like that. But I still ended up leaving before too long because it was too difficult and slow to make real change and progress.
So if you work for somewhere even worse than that, just leave!
I know a little about getting large companies to use unknown and "risky" tech. I've done it a number of times (including one I'm especially proud[0] of, and that is relevant given the Clojure connection), and built more than one billion-dollar product doing so.
Names have incredible power, positive or negative, when something is in its infancy.
At the start, when it's just you, and maybe one other person, and maybe one more than that... and your entire effort is just a wisp of what it could one day be, all it takes is some random fly-by-night architect (or even project manager) walking by, hearing the name, and saying, "No way am I letting something called jank touch this project," and shutting it down. The ol' swoop-and-poop, but for incredibly understandable reasons: corporate drones are superstitious.
Now... if, as a matter of culture building, you're intentionally leaning into the "jank" name, that's different. Because names have incredible power. So if you're cobbling together a cadre of crack hackers, "jank" might be exactly what you need to telegraph exactly the ethos you want to manifest.
But if you're just looking for a memorable name to slap on something you hope will actually get traction in any production capacity, I'd just ask that Jeaye consider if the potential benefits outweigh the risks.
I love Clojure, I really do, but it feels to me like what I once thought was its unstoppable march ground to a halt and then it kinda fell out of the nerd consciousness. I’m hopeful jank might be the shot in at arm Clojure needs to get going again.
Anecdotally, the JVM is exactly why I never gave Clojure a shot, despite being otherwise immensely interested.
I remember reading about Jank a while back on HN and got excited for it. Though I wonder if it’ll be too late to recapture my interest by the time it’s ready. Hopefully not.
There is also Janet. Not quite Clojure, but heavily inspired and a nice language. And it has a pretty small, mostly ANSI C implementation, easy and fast to compile anywhere I tried, and can be embedded in applications with no LLVM dependency.
I remember Clapp a Common Lisp in C++ using LLVM. Clapp was promising but progress has been very slow. Since Clojure is similar to CL, one wonder if Jank will experiment similar problems. Might I ask the author of Jank whether he knows about Clapp and if so, how will this project try to avoid getting stagnated?
In that post and comments we read that Clapp was 100x slower that sbcl, and the author of Clapp claimed: "LLVM is a great library for implementing C and C++ but more work needs to be done to support Lisp features like closures and first-class functions. We are working on that now".
I hope Clapp's author work in the last 11 years could help today efforts. Surely, the LLVM of today is not that of 11 years ago. Anyway, IMHO, sharing some knowledge could be productive for any project that is about C++, Lisp or Clojure using LLVM.
If I recall correctly, compiling Clapp takes a full day, that gives not a good vibe.
On the happy path, I think that Julia transpile to LLVM, but Julia is the result of many men working years at it. Honestly, I don't think that one single programmer to be able to create such a big project as a performant clojure in C++ will the ability to compile code quickly. Getting sbcl speed and compilation speed would be an extraordinary feat!
In Go there were great sacrifices to get fast compilation, and the problems to include generics, trying to avoid blows up compilation because some type checking is NP-complete.
Also perhaps ECL, a lisp in C, can gives us some hints about how to get better performance and compilation speed.
Perhaps I am just too old to be open to new dreams, anyway I hope the best to this project and I thank to Clojurists Together for supporting this project. It must be very intellectual rewarding to work in a project whose aim is to extend and improve your favorite computer language. But the journey will be no an easy one, that's for sure.
> Might I ask the author of Jank whether he knows about Clapp and if so, how will this project try to avoid getting stagnated?
I'm aware of Clasp and have spoken with drmeister about it in the early days of jank. Ultimately, jank and Clasp differ greatly, not only in that jank is Clojure and Clasp is Common Lisp, but also in their approach to C++ interop.
> If I recall correctly, compiling Clapp takes a full day, that gives not a good vibe.
I'm not sure about Clasp's compile times, but C++ is slow to compile, in general. The jank compiler itself builds from nothing in about 1 minute on my machine. We've yet to see how the jank compiler will handle large Clojure project, but I do expect it to be slower than Clojure JVM.
> In that post and comments we read that Clapp was 100x slower that sbcl
That's an old post, so I'd expect that Clasp is faster now. I can say that jank is not 100x slower than Clojure JVM, in my benchmarks.
> Perhaps I am just too old to be open to new dreams, anyway I hope the best to this project and I thank to Clojurists Together for supporting this project. It must be very intellectual rewarding to work in a project whose aim is to extend and improve your favorite computer language. But the journey will be no an easy one, that's for sure.
Thanks for the interest and kind words. It's not easy, but it's doable!
What is jank's tooling like? Clojure's felt very much like an afterthought, unfortunately. I do like having a decent set of tools right out of the box. I think Gleam was smart in this respect.
I'm especially excited about the error reporting in jank. Fingers crossed they will live up to the blog post showcasing them. Most people I convince to give Clojure a shot tell me that they are utterly confused about its error messages.
Since this appears to be the marquee feature (compared to the well regarded Clojure) it would be good to see some benchmarks comparing the JVM to LLVM versions.
This allows jank to offer the same benefits of REPL-based development while being able to seamlessly reach into the native world and compete seriously with JVM's performance.
I have blog posts with various benchmarks and optimizations, but ultimately all of my time is being spent actually developing the language right now. The fluidity of the implementation also means that the benchmarks from last year aren't really applicable anymore.
Performance measurement and optimization is something I thoroughly enjoy and look forward to being able to focus on once jank hits parity with Clojure and is stable enough to warrant performance as a priority.
How does programming with Clojure targeting multiple platforms (JVM, JS, CLR, LLVM, ...) work?
Are there Clojure libraries that don't use JVM(/JS/...)-specific stuff that works on any Clojure platform/dialect? Can such libraries be used on Jank out of the box? Or do library authors have to do something explicit in their libraries to enable their use in specific platforms/dialects?
> Are there Clojure libraries that don't use JVM(/JS/...)-specific stuff that works on any Clojure platform/dialect? Can such libraries be used on Jank out of the box?
Correct. Any Clojure code which doesn't use interop will generally work with Clojure, ClojureScript, Clojure CLR, jank, etc. There are some exceptions, where different dialects don't fully implement a Clojure feature, but this is generally the case.
> Or do library authors have to do something explicit in their libraries to enable their use in specific platforms/dialects?
Clojure also supports reader macros to enable forms for specific dialects. This is basically like an #ifdef in the C world, where library devs can check if the code is currently being compiled for Clojure, ClojureScript, jank, and so on. This allows you to have a public function, for example, which internally just uses a reader conditional to do the correct thing. For example:
(defn sleep [ms]
#?(:clj (Thread/sleep ms)
:jank (let [s (/ ms 1000)
ns (* (mod ms 1000) 1000000)
t (cpp/timespec. (cpp/long. s) (cpp/long. ns))]
(cpp/nanosleep (cpp/& t) cpp/nullptr))))
That's using the currently working C and C++ interop to call the POSIX C function. The same could be done for the C++ version. This function can now be used in both Clojure and jank with no difference to the consumer.
> How does programming with Clojure targeting multiple platforms (JVM, JS, CLR, LLVM, ...) work?
Each variant has its own file extension, e.g. .clj for JVM and .cljs for JS.
In case you're writing code that needs to work on multiple platforms, you put it in a .cljc file. Any of the code in these files that still needs to be different due to the platform choice is differentiated inline using a reader macro, which results in the different platform compilers getting a (slightly) different abstract syntax tree, so it is not too dissimilar from writing cross-platform code in other languages (just more convenient due to the Lisp style).
Once all of the necessary features are implemented, the only thing in the way will be JVM interop. If you have any "pure Clojure" code (i.e. no interop), it should also be valid jank code.
I am once again calling for someone to make something that's "basically clojure" but with whitespace instead of parens.
Parens don't matter, but giving values names causing indentation is something I simply cannot abide as a terminal intermediary value name giving person.
EDIT: oh and also something with "proper" multiline comments. Thanks!
I'm interested in exploring this as a potential dialect of jank. In the simplest approach, it's just a lexer change. However, there are implications around how macros will work, since macros generate s-expressions in Clojure, but they'd need to generate whitespace blocks if we want the homoiconicity to spread throughout.
If you're interested in funding this work, or helping with the designs, please reach out. In the meantime, make sure you check out Rhombus. https://rhombus-lang.org/
Right, it's hard to disconnect some of Clojure's niceties from the macro work (though I think Clojure's macros tend to do less inner inspection than macros you'll see in other lisp variants)
The link to Rhombus is very interested, I hadn't heard of this and it looks very well developed. Will mess around with it.
Are you sure you want that? It's possible to use whitespace and conventions to denote s-expr. For example, have each line implicitly start with a ( and use \n for ). More complicated schemes involving counting indentation also available. Anything you like that parses to a tree really.
The price will tend to be that working out what the code parses to gets much more difficult for the programmer. The parser won't care. Guessing what the programmer might have meant for error messages gets harder. Copying code around in a file will tend to change the meaning.
Worth noting that dear python, whitespace enthusiast, still has spurious : scattered around to give slightly better behaviour on syntactically invalid input, and they managed to compromise the semantics of lambda to keep the whitespace magic working.
An alternative you might like is colouring parens light grey (or dark, whatever mostly matches the editor background) and have the editor insert them for you.
https://yamlscript.org/about/ - YAMLScript (or YS) A graal pre-compiled Clojure environment, like Babashka, but uses a different, looser syntax (yaml based). Can run from command line.
This comes up all the time. I used to dismiss lisps and schemes as well because of parens until I've messed with Racket. My advice to you is to stop worrying and embrace parens. The REPL helps.
As an emacs user I'm often writing small snippets of elisp for my own needs, and for snippets it's fine. But I dislike what most lisp's scoping strategies do to "normal" indentation of code. Any function with even a single intermediate value, indented due to a `let`! It's noticably more symbols compared to my Python stuff.
On top of that, being unable to just slap in big docstrings in my code makes me sad. Docstrings are useful.
This isn't the end of the world, but it's something that bugs me.
Hey folks. I'm the creator of jank. I didn't expect to be on HN today, but I appreciate the interest.
In short, jank is Clojure, but it's on LLVM and has seamless C++ interop. You still get full nREPL capabilities, can redefine anything on the fly, and we can actually JIT compile C++ code alongside your Clojure. The seamless C++ interop is first of its kind, for a lisp, and is done by JIT compiling C++ alongside the LLVM IR we generate for jank and then stitching them together into one IR module.
Note, jank isn't released yet. I'm targeting the end of this year for the first alpha release. I put out monthly development updates on the jank blog, with the next one coming out this week.
I don't have anything terribly insightful to say other than that I really like this project. I loved using Clojure back during it's brief time in the sun, it was a great time, and jank has all the same awesome vibes. I haven't used it on anything serious yet, it seems like it would be great for like a `zig`-flavored approach to C++ builds for example. I'm doing a big C++ project ground up at the moment, I think I'm going to get jank into it and see if I can automate some stuff.
The compilation strategy is very much what Carmack did in Trinity and whether you got your inspiration there or independently had a great idea, that's good company to be keeping.
Keep it up!
beautiful work. clojure is very nice. one of the most impactful talks I have ever seen was from Rich Hickey - simple made easy.
however my only gripe with clojure while it's easy to write and comprehend at first - it's difficult to read. & yet most our time we read code not write it. but then again it might be my lack of brain power.
I agree with you, but perhaps in my own way. Jumping into an arbitrary Clojure program can be tough, since the data shapes may not be defined anywhere. Hopefully the program uses spec or malli, but even then, unless they annotate every function with the shape it expects, you may be left needing to REPL in and poke around. However, REPLing in to check just a function or two may not be easy if the program requires some setup and doesn't use integrant or similar.
Once Clojure parity is achieved, I'm interested in static typing, pattern matching, value-based errors, and some other opt-in improvements that I think will greatly improve both readability and toolability (i.e. how well tooling can work with the code, based on what it knows of the code). Stay tuned. :)
If you can run it, then you can REPL it, no matter how deeply nested. Scope-capture (https://github.com/vvvvalvalval/scope-capture) has been probably the most important tool in my box. Hope jank supports it eventually.
Thanks for jank! It’s great to be reading about it, listening to you talking about it at conferences, and I can’t wait to try it out!
What’s your take on Hickey’s talk titled “Maybe Not” which fundamentally criticizes static types? Is there a middle ground where some form of static typing makes sense in a Clojure-esque world?
https://youtu.be/YR5WdGrpoug?si=4mI8doBX6jj6PJkk
Heh. Hickey once debated with me at length about visual neuroscience, a subject I have a master's degree in and he doesn't. At no point did this stop him from confidently asserting things.
I have to wonder if "Maybe Not" is similar, since he's not actually an expert in types, either afaik.
Alexis King wrote a partial rebuttal to Maybe Not: https://lexi-lambda.github.io/blog/2020/01/19/no-dynamic-typ...
I'd personally say typing or not is a style choice, but your criticism here seems to be that Hickey doesn't have a visual neuroscience Master's degree which seems a bit arbitrary.
If your argument is you are an expert but Hickey is not, criticising him on his language design skills seems like a logical mistake. He's one of the foremost language designers of the current era. "Maybe Not" is a speech by an expert talking in his field of expertise.
If your argument is that his confidence is unfounded, again, he's an expert talking in his area. He can reasonably take a confident attitude in that, even if he has unfounded confidence in other fields he isn't an expert in. Lots of experts do that, it is a well founded stereotype of smart people.
He doesn't need to be an expert in implementing types to judge whether they are a good language feature.
Being an expert in type theory or language design is not particularly relevant either. The most relevant expert would be the lowly maintenance developer.
Rich has many great ideas and he founded Clojure. I respect him deeply. On typing, however, we do not agree entirely.
For a practical example of a Clojure-like language with a completely static type system (with affine typing), see Carp. https://github.com/carp-lang/Carp
I don't see why there can't be a Carp mode in jank, with bridges in place to connect the Clojurey world from the Carpy world. This would allow jank users to develop interactively to start with, figure out their shapes, use the REPL, etc. Then, if they want, they can lock down some parts of the code for both performance and correctness gains.
FWIW, (I have one Clojure project I inherited at work that my team maintains) I love this direction.
Been a while since I've watched/read it, but I remember the ideas in Maybe Not being quite interesting.
To me, the really important idea wasn't a criticism of static types in general.
Instead it was the idea that static typing in most (all?) mainstream implementations conflates concepts that should be separate, specifically the shape of the information that we have (e.g. what fields of what types), and whether a particular bit of information is available and required (e.g. nullability).
He contends that the former belongs in our usual "type definition", whereas the latter relates instead to a given context. For example, my PassportForm type always has a date-of-birth field in its _shape_, but whether it's statically required/present to exist depends on whether we're at a HTTP API boundary, an internal domain function boundary, writing into a database.
It sounded like that kind of "nullability masking" was intended as a feature of Spec, but I don't get the impression it was ever implemented.
I don’t think Rich was criticizing static types as much as saying that they aren’t giving you as much benefit as you think they are and that they complicate program evolution over time.
I don't think static types (or lack thereof) complicate (or simplify) program evolution over time, bad architecture does.
They might not give much execution benefit and they may indeed complicate program evolution, but they DO aid readability, document-ability and refactoring!
Still quite the hot take.
Indeed.
> pattern matching, value-based errors
I did not know these were in the cards, that makes jank even more exciting!
My comment to code ratio is magnitudes higher in Clojure than in other languages, which helps a lot with this.
Also writing Clojure can be incredibly terse, resulting in quite high-effort when reading. Conversely, a lot of time I can condense hundreds of lines of equivalent python into 5 or 6 lines of Clojure. Having all of this functionality condensed into something you can fit in a tweet really helps for grokking larger parts of the dataflow or even larger system. So there are tradeoffs
Plus structural editing and the repl really help with the “reading” experience (reading in quotes because it’s much more interactive than reading)
> Conversely, a lot of time I can condense hundreds of lines of equivalent python into 5 or 6 lines of Clojure.
I'm curious if you have any example of this? Even if it's an hyperbole, I don't really see how.
In my (limited) experience with Clojure and other functional languages, this is usually true under situations where:
1. You’re mapping or reducing some dataset
2. Your iteration logic does not branch a lot
3. You can express your transformation logic using higher order functions (e.g. mapping a reduction operation across a multidimensional array)
Some domains have a log of this style of work—finance comes to mind—others do not. I suspect this is why I’ve personally seen a lot more of Clojure in finance circles than I have in other industries.
Maybe hyperbole on the frequency, but not the condensation. I meant more along the lines of “most of the complicated code I write in Clojure is an order of magnitude more dense.” _Most_ of the code I write would be 1:1 or 1:2 with other languages, it I don’t think it’s the type of code OP was referring to.
The 1:20+ is definitely not hyperbole though. Using transducers to stream lazy reductions of nested sequences; using case, cond-> and condp->; anywhere where you can lean on the clojure.core library. I don’t know how to give specific examples without giving a whole blog post of context, but 4 or 5 examples from the past year spring to mind.
It’s also often the case that optimizing my clojure code results in a significant reduction of lines of code, whereas optimizing Python code always resulted in an explosion of LoC
Personally I find Python particularly egregious. No map/filter/reduce, black formatting, no safe nested property access. File length was genuinely one of the reasons I stopped using it. The ratio would not be so high with some languages, ie JavaScript
Even with Elixir though, many solutions require 5-10 times the amount of lines for the same thing thing in Clojure. I just converted two functions yesterday that were 6 & 12 lines respectively in Clojure, and they are both 2 pages in Elixir (and would have been much longer in Python)
I find 95% Clojure has the right tools to write very terse code. But in some cases the functional transducer/piped paradigm can't be contorted to the problem.
Usually these are problems where you need to run along a list and check neighboring elements. You can use amap or map-indexed but it's just not ergonomic or Clojure-y (vs for instance the imperative C++ iterator model)
The best short example I can think of is Fibbonacci
https://4clojure.oxal.org/#/problem/26/solutions
I find all the solutions hard to read. They're all ugly. Their performance characteristics are hard to know at a glance
Personally, I would normally reach for loop to check neighboring elements very ergonomically.
There’s also partition if you're working with transducers/threads/list comprehension Or if you need to apply more complicated transformations to the neighbors/cycle the neighbors Using map-indexed to look up related indices is something I don’t think I do anywhere in my codebase. Agreed that it’s not ergonomicEDIT: those Fibonacci functions are insane, even I don’t understand most of them. They’re far from the Clojure I would advocate for, most likely written for funsies with a very specific technical constraint in mind
Yeah, I guess partition to me always looks like a dangerous tool - for instance I have a sequence of numbers and I want to do a 5 point rolling average
You could do `(partition 5 1 coll)` and then average each element in the resulting seq.. It's very easy to reason about. But I'm guessing the performance will be abysmal? You're getting a lazy list and each time you access a 5 neighbor set.. you're rerunning down you coll building the 5 unit subsets? Maybe if you start with an Array type it'll be okay, but you're always coercing to seq and to me it's hard
Taking the first 5 elements, recurring on a list with the top element dropped is probably better, but I find the code hard to read. Maybe it's a familiarity issue..
Yeah like I said I reach for loop first and foremost. This is what it would look like with comments if it were actually something complicated (although the comments are quite trivial here):
Realistically if performance was a consideration I would probably do: Should be ~15 times faster to avoid the nested loop. If you want to change the min size it's still pretty clean:Exactly. I would use loop or partition+map/reduce for that case. I almost never use map-indexed. In fact I almost never use indexing at all. Mostly, when I have a sequential collection (vector, list, or generic seq), I need to iterate over all the elements, and I’m doing that with map or reduce. IMO, map-indexed has a code-smell that indicates that you’re reaching for an imperative algorithm when perhaps a functional algorithm would be better. Surely, there are times when map-indexed is just what you need, which is why it’s there, but typically not in my experience.
I would agree that map-indexed + *nth* is a code smell. I use map-indexed all the time though, just when I need the index, not other elements in the list
Maybe I'm just too used to Python (and I only know some Clojure) but I don't have the same experience. Usually using generators and itertools will really help you shorten your code. I'm working in a data science adjacent field so a lot of code is just frameworks anyways but I don't feel limited in pure Python either.
If you come across a post or an example that shows those differences, I would be very interested!
Could you show an example or two between Elixir and Clojure?
This is not the best example, it's just the most recent example (what I was doing last night) that can fit in one screen:
The elixir code I was able to condense down into: It was much longer prior to writing this comment (I originally used multiple arity helper functions), but it was only fair I tried my best to get the elixir version as concise as possible before sharing. Still 2x the lines of effective code, substantially more verbose imho, and required dedicated (minor) golfing to get it this far.Replacing this report function (12 lines) + one other function (6 lines) + execution code (18 lines) is now spread across 3 modules in Elixir, each over 100 lines. It's not entirely apples to oranges, but trying to provide as much context as possible.
This is all just to say that the high effort in reading it is normally a result of information density, not complexity or syntax. There are real advantages to being able to see your entire problem space on a single page.
Why call it jank? It is a negative associated word in most contexts that’s why I’m curious about it
I guess it stands out. "Git" is similar. "Rust" isn't a very positive word either. Perhaps it's a new trend. Maybe the answer is "all the good names have been taken" and/or they are simply lazy.
I view "jank" similarly to "cracked", not necessarily negative.
Yep I won’t use anything with a negative self deprecating name like this. Because some tech bro will use it as a a basis to disqualify my entire resume or sabotage an interview after solving the leetcode trivia troll questions and whatever other video game battles they add to the interview process in the future.
Project manager fires the entire team except 1 intern to finish the project with 1000 points of stories in 1 sprint? Heh or did you just figure out jank wasn’t capable of doing the job what did you expect?
Hotfix to fix a bug with the stage environment because the SREs set it up wrong? No bro it’s jank it’s that jank thing. Source: ctrl F “jank” in the message analytics and copilot says all matches are in the stage environment and that jank is also a tech thing. It also bright up every engineers profile that lists jank as a skill. Time to pick a scape goat.
Is tech bro in the room with us right now?
I’m a bit curious why you chose to implement this as a different language (even though it implements Clojure) instead of an alternative Clojure backend and/or C++ syntax extension.
Do you plan to make Windows support first-class? I think a lot of people looking at LLVM based languages are interested in alternatives to C++ for games.
> I’m a bit curious why you chose to implement this as a different language (even though it implements Clojure) instead of an alternative Clojure backend and/or C++ syntax extension.
jank is Clojure. However, the Clojure name is trademarked and using it requires permission which I don't have. Furthermore, I want to build upon the Clojure base to provide more, going forward. That may include static typing, value-based error handling, first class pattern matching, and so on. Those would be opt-in features on top of Clojure. All of these reasons lead me to not use Clojure in the name (like Clojure++, ClojureNative, etc).
> Do you plan to make Windows support first-class? I think a lot of people looking at LLVM based languages are interested in alternatives to C++ for games.
Indeed, a lot of game dev folks use Windows. Right now, jank's Windows support is limited. My initial audience is Clojure devs who want native access and lighter binaries. Once that launch has stabilized, I will focus on appealing to existing native devs who want to embed an interactive, functional language into their C++ applications. That will requires strengthening the Windows support, establishing stable native APIs, and writing the onboarding material for lisp, REPL-based editing, data-driven design, and so on. This is a much larger task, which is why I'm focusing on existing Clojure devs first.
I commented to this effect on Reddit, but my interest is entirely conditional on ability to embed Jank into a pre-existing C++ application as a shared library.
Ideally without controlling the code of the main application (e.g. to implement a plug-in).
Yep, this will be an important use case and will be officially supported. For the first alpha release this year, I'm focusing on Clojure devs, but support for existing native devs will come once things stabilize.
Shouldn't it be an 'if' instead of 'when' in the first example?
Yes it should. Thanks for the keen eye and taking the time to point that out.
Hey, as someone who spent a few years reimplementing another language trying to decouple it from JVM (Scala JVM -> Scala Native), some pitfalls to avoid:
- Don't try to provide backwards compatible subset of JVM APIs. While this might seem tempting to support very important library X with just a bit of work, I'd rather see new APIs that are only possible with your language / runtime. Otherwise you might end up stuck in never-ending stream of requests to add one more JVM feature to get yet another library from the original JVM language running. Focus on providing your own unique APIs or bindings to native projects that might not be easy to do elsewhere.
- Don't implement your own GC, just use mmtk [1]. It takes a really long time to implement something competitive, and mmtk already has an extensible and pluggable GC design that gets some of the best performance available today [2] without much effort on your end.
- Don't underestimate complexity and importance of multi-threading and concurrency. Try to think of supporting some form of it early or you might get stuck single threaded world forever (see CPython). Maybe you don't do shared memory multi threading and then it could be quite easy to implement (as in erlang). No shared memory also means no shared heap, which makes GCs's life much easier.
- Don't spend too much time benchmarking and optimizing single threaded performance against JVM as performance baseline. If you don't have a compelling use case (usually due to unique libraries), the performance might not matter enough for users to migrate to your language. When you do optimize, I'd rather see fast startup, interactive environment (think V8), over slow startup but eventually efficient after super long warmup (like jvm).
I see that jank is already doing at least some of the things right based on the docs, so this message might be more of a dump of mistakes I've done previously in this space.
[1]: https://github.com/mmtk/mmtk-core
[2]: https://dl.acm.org/doi/pdf/10.1145/3519939.3523440
I love this project. I've been a sponsor on GitHub since late last year.
But for the love of... please pick a different name.
Whatever reasons companies/teams will have for not letting someone use Jank at work, don't let the name be one of them.
You're in luck ;)
https://jank-lang.org/blog/2025-04-01-jank-has-been-renamed/
The "one letter danger" section is hilarious, but did you try to find any examples with a one-vowel difference?
Grody
I love the name Jank. I would use it just for the name alone.
What's the demonym for Jank devs? Janker?
Still deciding. Maybe jankster.
Jankobite? Ehhh
I love this project, and frankly I can't wait until I see Zig code stitched into and interoperating in a lisp via C transpilation, but I really do agree with the top commenter if you can't get Clojure trademark approval.
Anyways, keep up the amazing work, I wish I could have seen your janky talk at Strangeloop on another timeline.
I suppose jank-yanker is off the table.
Jankee seems like a perfectly janky demonym.
The cute form would be Jankiye
Jankobian?
Jankbroni
Yes but pronounced in the Nordic and Central European fashion (“yanker”)
When there's a book, whoever does the illustrations should be a jankee doodle.
... Right, I'll show myself out.
It can get adapted to a Broadway musical and named Damn Jankees
Jankoffs
/s
What's the objection to the name? I don't get it.
Has negative connotations
https://fluentslang.com/jank-meaning/
Kind of like the name "Slack", which also keeps its product from being uses in enterprise settings? /s
My company had what was at the time one of the largest Slack enterprise contracts. You have no idea what internal corporate battles we had to face to get our higher-ups to take us seriously at every stage of adoption, and ultimately roll it out en masse. Slack succeeded in enterprise in spite of its name, not because of it. The actual product was phenomenal, relative to alternatives.
or "Git", which is a straight insult.
Yes, when you have the notoriety, distribution, and reputation-for-insults that Linus does, you can get away with things like that, because you're selling into a culture that already understands the "joke".
Janky - slang for something shit, crsp, cobbled together haphazardly...?
I worked for a while in a big traditional corporation. My team was a bit like a little enclave inside the larger organisation. They knew us because we had our top shirt buttons undone and wore brown shoes instead of black. When we interacted with the traditional suits the worst we got were chuckles and eye rolls as we said names like "Python", "GIMP" and "Cockroach" instead of the things they knew about like SAS and Oracle. We never met any resistance due to naming or anything like that. But I still ended up leaving before too long because it was too difficult and slow to make real change and progress.
So if you work for somewhere even worse than that, just leave!
I know a little about getting large companies to use unknown and "risky" tech. I've done it a number of times (including one I'm especially proud[0] of, and that is relevant given the Clojure connection), and built more than one billion-dollar product doing so.
Names have incredible power, positive or negative, when something is in its infancy.
At the start, when it's just you, and maybe one other person, and maybe one more than that... and your entire effort is just a wisp of what it could one day be, all it takes is some random fly-by-night architect (or even project manager) walking by, hearing the name, and saying, "No way am I letting something called jank touch this project," and shutting it down. The ol' swoop-and-poop, but for incredibly understandable reasons: corporate drones are superstitious.
Now... if, as a matter of culture building, you're intentionally leaning into the "jank" name, that's different. Because names have incredible power. So if you're cobbling together a cadre of crack hackers, "jank" might be exactly what you need to telegraph exactly the ethos you want to manifest.
But if you're just looking for a memorable name to slap on something you hope will actually get traction in any production capacity, I'd just ask that Jeaye consider if the potential benefits outweigh the risks.
[0]: https://www.linkedin.com/pulse/building-cloud-choosing-lisp-...
I love Clojure, I really do, but it feels to me like what I once thought was its unstoppable march ground to a halt and then it kinda fell out of the nerd consciousness. I’m hopeful jank might be the shot in at arm Clojure needs to get going again.
I blame the jvm for this. Happy to see an implementation on llvm.
Anecdotally, the JVM is exactly why I never gave Clojure a shot, despite being otherwise immensely interested.
I remember reading about Jank a while back on HN and got excited for it. Though I wonder if it’ll be too late to recapture my interest by the time it’s ready. Hopefully not.
There is also Janet. Not quite Clojure, but heavily inspired and a nice language. And it has a pretty small, mostly ANSI C implementation, easy and fast to compile anywhere I tried, and can be embedded in applications with no LLVM dependency.
I remember Clapp a Common Lisp in C++ using LLVM. Clapp was promising but progress has been very slow. Since Clojure is similar to CL, one wonder if Jank will experiment similar problems. Might I ask the author of Jank whether he knows about Clapp and if so, how will this project try to avoid getting stagnated?
Edited: Here is a post in HN from 2014 about Clapp. https://news.ycombinator.com/item?id=8367404
In that post and comments we read that Clapp was 100x slower that sbcl, and the author of Clapp claimed: "LLVM is a great library for implementing C and C++ but more work needs to be done to support Lisp features like closures and first-class functions. We are working on that now".
I hope Clapp's author work in the last 11 years could help today efforts. Surely, the LLVM of today is not that of 11 years ago. Anyway, IMHO, sharing some knowledge could be productive for any project that is about C++, Lisp or Clojure using LLVM.
If I recall correctly, compiling Clapp takes a full day, that gives not a good vibe.
On the happy path, I think that Julia transpile to LLVM, but Julia is the result of many men working years at it. Honestly, I don't think that one single programmer to be able to create such a big project as a performant clojure in C++ will the ability to compile code quickly. Getting sbcl speed and compilation speed would be an extraordinary feat!
In Go there were great sacrifices to get fast compilation, and the problems to include generics, trying to avoid blows up compilation because some type checking is NP-complete.
Also perhaps ECL, a lisp in C, can gives us some hints about how to get better performance and compilation speed.
Perhaps I am just too old to be open to new dreams, anyway I hope the best to this project and I thank to Clojurists Together for supporting this project. It must be very intellectual rewarding to work in a project whose aim is to extend and improve your favorite computer language. But the journey will be no an easy one, that's for sure.
> Might I ask the author of Jank whether he knows about Clapp and if so, how will this project try to avoid getting stagnated?
I'm aware of Clasp and have spoken with drmeister about it in the early days of jank. Ultimately, jank and Clasp differ greatly, not only in that jank is Clojure and Clasp is Common Lisp, but also in their approach to C++ interop.
> If I recall correctly, compiling Clapp takes a full day, that gives not a good vibe.
I'm not sure about Clasp's compile times, but C++ is slow to compile, in general. The jank compiler itself builds from nothing in about 1 minute on my machine. We've yet to see how the jank compiler will handle large Clojure project, but I do expect it to be slower than Clojure JVM.
> In that post and comments we read that Clapp was 100x slower that sbcl
That's an old post, so I'd expect that Clasp is faster now. I can say that jank is not 100x slower than Clojure JVM, in my benchmarks.
> Perhaps I am just too old to be open to new dreams, anyway I hope the best to this project and I thank to Clojurists Together for supporting this project. It must be very intellectual rewarding to work in a project whose aim is to extend and improve your favorite computer language. But the journey will be no an easy one, that's for sure.
Thanks for the interest and kind words. It's not easy, but it's doable!
Clasp, not Clapp, and it's still getting releases. 2.7.0 was released in January, and 2.8.0 is pending.
https://github.com/clasp-developers/clasp
Related: The janet programming language https://janet-lang.org/
What is jank's tooling like? Clojure's felt very much like an afterthought, unfortunately. I do like having a decent set of tools right out of the box. I think Gleam was smart in this respect.
Was a little surprised to discover this was a real language and not an esoteric or joke language.
I'm especially excited about the error reporting in jank. Fingers crossed they will live up to the blog post showcasing them. Most people I convince to give Clojure a shot tell me that they are utterly confused about its error messages.
Yeah it’s pretty incredible how unhelpful the error messages are. A true feat. Still love clojure but wow
Since this appears to be the marquee feature (compared to the well regarded Clojure) it would be good to see some benchmarks comparing the JVM to LLVM versions.
This allows jank to offer the same benefits of REPL-based development while being able to seamlessly reach into the native world and compete seriously with JVM's performance.
I have blog posts with various benchmarks and optimizations, but ultimately all of my time is being spent actually developing the language right now. The fluidity of the implementation also means that the benchmarks from last year aren't really applicable anymore.
Performance measurement and optimization is something I thoroughly enjoy and look forward to being able to focus on once jank hits parity with Clojure and is stable enough to warrant performance as a priority.
how does memory management work? malloc and such?
I feel like I’ve been coding jank already for most of my life.
Welcome home.
How does programming with Clojure targeting multiple platforms (JVM, JS, CLR, LLVM, ...) work?
Are there Clojure libraries that don't use JVM(/JS/...)-specific stuff that works on any Clojure platform/dialect? Can such libraries be used on Jank out of the box? Or do library authors have to do something explicit in their libraries to enable their use in specific platforms/dialects?
> Are there Clojure libraries that don't use JVM(/JS/...)-specific stuff that works on any Clojure platform/dialect? Can such libraries be used on Jank out of the box?
Correct. Any Clojure code which doesn't use interop will generally work with Clojure, ClojureScript, Clojure CLR, jank, etc. There are some exceptions, where different dialects don't fully implement a Clojure feature, but this is generally the case.
> Or do library authors have to do something explicit in their libraries to enable their use in specific platforms/dialects?
Clojure also supports reader macros to enable forms for specific dialects. This is basically like an #ifdef in the C world, where library devs can check if the code is currently being compiled for Clojure, ClojureScript, jank, and so on. This allows you to have a public function, for example, which internally just uses a reader conditional to do the correct thing. For example:
That's using the currently working C and C++ interop to call the POSIX C function. The same could be done for the C++ version. This function can now be used in both Clojure and jank with no difference to the consumer.> How does programming with Clojure targeting multiple platforms (JVM, JS, CLR, LLVM, ...) work?
Each variant has its own file extension, e.g. .clj for JVM and .cljs for JS.
In case you're writing code that needs to work on multiple platforms, you put it in a .cljc file. Any of the code in these files that still needs to be different due to the platform choice is differentiated inline using a reader macro, which results in the different platform compilers getting a (slightly) different abstract syntax tree, so it is not too dissimilar from writing cross-platform code in other languages (just more convenient due to the Lisp style).
Aside from the lack of JVM, what's holding back Jank from being a drop in Clojure replacement?
Once all of the necessary features are implemented, the only thing in the way will be JVM interop. If you have any "pure Clojure" code (i.e. no interop), it should also be valid jank code.
I am once again calling for someone to make something that's "basically clojure" but with whitespace instead of parens.
Parens don't matter, but giving values names causing indentation is something I simply cannot abide as a terminal intermediary value name giving person.
EDIT: oh and also something with "proper" multiline comments. Thanks!
I'm interested in exploring this as a potential dialect of jank. In the simplest approach, it's just a lexer change. However, there are implications around how macros will work, since macros generate s-expressions in Clojure, but they'd need to generate whitespace blocks if we want the homoiconicity to spread throughout.
If you're interested in funding this work, or helping with the designs, please reach out. In the meantime, make sure you check out Rhombus. https://rhombus-lang.org/
Right, it's hard to disconnect some of Clojure's niceties from the macro work (though I think Clojure's macros tend to do less inner inspection than macros you'll see in other lisp variants)
The link to Rhombus is very interested, I hadn't heard of this and it looks very well developed. Will mess around with it.
Are you sure you want that? It's possible to use whitespace and conventions to denote s-expr. For example, have each line implicitly start with a ( and use \n for ). More complicated schemes involving counting indentation also available. Anything you like that parses to a tree really.
The price will tend to be that working out what the code parses to gets much more difficult for the programmer. The parser won't care. Guessing what the programmer might have meant for error messages gets harder. Copying code around in a file will tend to change the meaning.
Worth noting that dear python, whitespace enthusiast, still has spurious : scattered around to give slightly better behaviour on syntactically invalid input, and they managed to compromise the semantics of lambda to keep the whitespace magic working.
An alternative you might like is colouring parens light grey (or dark, whatever mostly matches the editor background) and have the editor insert them for you.
This exists. https://yamlscript.org/blog/2025-06-24/how-does-ys-work/
https://yamlscript.org/about/ - YAMLScript (or YS) A graal pre-compiled Clojure environment, like Babashka, but uses a different, looser syntax (yaml based). Can run from command line.
Like a nicer Python?
I do not believe I want to write my code in something that goes through a YAML parser.
This comes up all the time. I used to dismiss lisps and schemes as well because of parens until I've messed with Racket. My advice to you is to stop worrying and embrace parens. The REPL helps.
As an emacs user I'm often writing small snippets of elisp for my own needs, and for snippets it's fine. But I dislike what most lisp's scoping strategies do to "normal" indentation of code. Any function with even a single intermediate value, indented due to a `let`! It's noticably more symbols compared to my Python stuff.
On top of that, being unable to just slap in big docstrings in my code makes me sad. Docstrings are useful.
This isn't the end of the world, but it's something that bugs me.
Great work! Also, the error messages are neat!
Good luck, jeaye.