ClojureScript on Emacs

A sign of good abstractions can be that they allow you to quickly breed useful new abstractions. This is accented by the feeling that your programming environment is working for you, and not against you. The situation where you are only limited by your thoughts and imagination, not what the computer is willing to allow you to do. It’s a feeling that I rarely capture outside of using Emacs. It’s a common joke that Emacs is a great operating system — it just lacks a decent text editor. Usually this is used in a backhanded way, but I’ve always viewed it as a strength. Emacs never tells me “No”, which is both a great danger and an incredible asset. In that spirit of development, I love to push Emacs to new limits to see what it is capable of… which as you will read now includes ClojureScript.

Emacs’ greatest strength is usually it’s largest source of criticism by non-users, which is the fact that it is controlled by a language called Emacs Lisp (elisp). elisp is a great language, but a very mediocre Lisp. Emacs’ users have wanted an alternative for a long time. emacs-ng is an emacs fork that integrates the Deno runtime via a set of additive changes to the native layer. This enables full scripting control of the editor using JavaScript/TypeScript while still being able to merge patches from upstream cleanly. This choice has been… controversial to say the least. A number of Emacs power users believe that Emacs IS lisp, and a large number of JavaScript users are perfectly fine with solutions like VSCode. What if there was a way to give everyone want they want?

Users have wanted to incorporate other lisps into Emacs for a long time. Clojure is a Lisp dialect that runs on the JVM. The usual discussion on integrating something like Clojure into Emacs is focused on the changes to the garbage collector and elisp runtime that would be required. Both of those systems are tightly coupled in Emacs, and changing them would be very difficult. It would take someone motivated that has a solid understanding of the existing C codebase and low level programming in general, that also understands Clojure. Individuals with that skillset are rare. I know I personally do not fit that bill… but why do I have to go through all that work in the first place? Clojure is a dynamic language, elisp is a dynamic language, why can’t the computer just figure out how they can talk to each other? To tie it back to the beginning, we just lack the right abstraction. Really I should say that we lacked the right abstraction. Someone got the nice idea that it would be awesome if run Clojure on the browser by compiling Clojure to JavaScript and call it ClojureScript. Since we now have the ability to run JavaScript in emacs-ng, does that mean we can run ClojureScript in Emacs?

I initially thought it would be impossible, due to the fact that ClojureScript does not support Deno as a first class target, but it turns out that GitHub user jobez actually got a MVP working, which I forked to play around with. The initial MVP is very simple, just compile ClojureScript as an ES6 module and import it. However, jobez set us up with something a little fancier — we can set up a file watcher compiler so we will generate our output on save. Then we just need to evaluate our JavaScript buffer to run our functions. I added a little logic to work around Deno’s lack of hot module reloading, but the end result is pretty straight forward:

It may not be clear since we have so many languages on the screen, but we are calling elisp functions from ClojureScript, and calling ClojureScript functions from JavaScript. That .message js/lisp is actually invoking elisp’s message function — and that isn’t special. We can invoke practically ANY elisp function from JavaScript, and so we can do the same thing for practically any elisp function. All of this comes from the wonderful lisp object in JavaScript, which allows us to effortlessly convert our JavaScript objects to elisp objects, and handles conversion vice versa. With that single capability, we gained an entire other language, in a few lines of code. The barriers to “what I want” melt away when the computer works for me instead of against me. With very little effort, we can export this as a proper elisp function and call it from *scratch*

Just like that, I can run ClojureScript from emacs-ng

The biggest limitation to this approach is that since Deno and nodejs have different APIs for file access, certain operations like file access will need some special code, i.e. you will need to use js/Deno readTextFile instead of using ClojureScript’s standard library and relying on the translation the ClojureScript compiler provides.

Performing a basic Fibonacci benchmark gives us the following (ClojureScript on top)

There’s some overhead here, but we are still well ahead of elisp

Output is in milliseconds for fib(40). This benchmark isn’t very in depth, and I’m sure that I could micromanage it further, but it is very promising.

There is still a lot to explore for this project, but I am very excited over it. I hope that people will leverage JavaScript/TypeScript and ClojureScript to make some amazing elisp applications on emacs-ng in the future.

Rust/C/C++ Systems Engineer, Game Engine Developer