There was recently a thread on the OCaml Discourse forum about sample Emacs plugins using the Ecaml library. Since the library doesn’t seem to have been getting that much use, and since I have some personal interest1 in seeing it used more, I decided to help by writing a Getting Started guide. Here goes.
This is part 1. We’ll see why you might use Ecaml to write a plugin, and get it set up and running with a simple Hello World. All of the code for this series is available on GitHub.
(This guide has only been tested on macOS 10.12, but probably works on other Unices like Linux or OS X. Unfortunately I have no idea about Windows.)
Table of Contents
Emacs is a great
text editor operating system habitat. Emacs’s power and
popularity arguably stem from its ability to be endlessly customized and
enhanced by plugins (i.e., extensions, packages, modules)—for example, I’m
writing this post in
org-mode while editing its YAML metadata (in the same
yaml-mode. These two editing modes are able to play nicely in the
same buffer thanks to a package called
mmm-mode (“multiple major modes”…
In this series, we’re going to write an interpreter for a minimalist Turing-complete programming language. We want to be able to run the code we’re editing directly within Emacs, instead of switching to our shell and running it from there, so we’ll embed our interpreter as a plugin in Emacs.
In the past, Emacs plugins have all been written in a language called Emacs Lisp (or Elisp), which, as its name suggests, is a Lisp. Elisp is a perfectly nice language, especially for writing the sort of code you might find in a plugin, but it has a couple of drawbacks. The main one I’m interested in is speed.
Ya see, Emacs’s Lisp interpreter ain’t exactly the fastest. A naïve speed comparison between Elisp and Ruby, for dramatic effect:
(benchmark-run (print (loop for i below 1000000 sum i))) ;; 499999500000 ;; (0.389676 0 0.0)
time ruby -e 'p (0...1000000).reduce(&:+)' # 499999500000 # ruby -e 'p (0...1000000).reduce(&:+)' 0.17s user 0.03s system 81% cpu 0.236 total
Both of these snippets just iterate from 0 to 999,999, adding up
The Elisp code takes quite a bit longer to add up the natural numbers less
than one million, and that’s including the startup time of the
interpreter! Now, to be fair, when compiled, the above Elisp code only takes
around 0.125 seconds, but that’s still substantially slower than compiled
languages like C or Java (or OCaml).
Emacs 25 added the ability to load native code plugins. Native code refers to code that runs directly on the processor, instead of being interpreted by another program, like Emacs’s Lisp interpreter or Ruby’s virtual machine.
There’s quite a good introduction to the basics of writing native plugins
here, but the gist of it is that Emacs exposes a small C API which you can
write modules against. Compiling them to shared objects (i.e., dynamically
.dll’s, etc.) allows Emacs to dynamically load
the plugin at runtime.
OCaml is a statically-typed functional programming language with a great deal of versatility, contrary to what you might have first expected when you just read the words “statically-typed” and “functional”. Of course, since you’re reading this blog post, I assume you are familiar with OCaml and won’t belabor singing its praises.
I’ll just point out that OCaml has a quite nice way to interface with C code using its foreign function interface (FFI), which means we can use OCaml to write native plugins for Emacs, as long as we have some library to provide the interface between the Emacs C API and our OCaml plugin. That’s where Ecaml comes in.
Ecaml is an open-source OCaml library for writing native Emacs plugins, available on OPAM or Jane Street’s GitHub page. We’ll use Ecaml to write our Emacs plugin, in OCaml instead of Elisp.
mkdir ~/ecaml-bf cd ~/ecaml-bf git init # or hg, if you prefer touch jbuild-workspace
We’ll use the development version of Ecaml, since the version in the OPAM repository is significantly outdated. Jane Street hosts an OPAM repo with the development versions of their open-source libraries, so let’s add the repo to OPAM:
opam switch 4.05.0 opam repo add janestreet-bleeding https://ocaml.janestreet.com/opam-repository opam install ecaml
We’ll start with Hello World for now (
let () = Ecaml.message "Hello from OCaml"; Ecaml.provide (Ecaml.Symbol.intern "ecaml-bf") ;;
Our simple plugin just writes a message to the echo area (or standard error
if Emacs is run in
--batch mode). Emacs plugins need to
provide the name
of the plugin in order to be loaded successfully by
require, so we do that
just before our plugin exits.
Note that OCaml’s module initialization (i.e., the stuff that runs when you compile and execute a normal OCaml program) serves as the code that is run when the plugin is loaded. In order to extend Emacs’s behavior while running, we’ll need to define some Elisp functions and key bindings to call those functions, later. For now, let’s just try our plugin out.
Unfortunately, I haven’t yet been able to figure out a way to get
to build shared objects. However, taking advantage of the fact that
executable files and shared objects have basically the same file format
(e.g., Mach-O or ELF), we can just pretend that the compiled executable is a
shared object. It’s fine, trust me. Probably.
We’ll set up our
jbuild file like so:
(jbuild_version 1) (executables ((names (plugin)) (libraries (ecaml)))) (rule (copy plugin.exe ecaml-bf.so)) (alias ((name plugin) (deps (ecaml-bf.so)))) (alias ((name runtest) (deps ((alias plugin))) (action (run emacs -Q -L . --batch --eval "(require 'ecaml-bf)"))))
copy rule which simply copies the
executable file to another name ending in
.so. This is the filename
extension that Emacs will look for when we try to load our plugin.
We’ll also define a
runtest action that will run Emacs in batch mode and
require our plugin. (I added the
-Q flag to skip user initialization,
etc. No need to load all of my Spacemacs config just to run a small test in
batch mode!). Let’s try it out:
jbuilder build @plugin 2>/dev/null && jbuilder runtest # Hello from OCaml # "Loaded Ecaml."
As you can see, the plugin printed out our message to standard error. It also
printed out a message from Ecaml itself. Normally these messages just end up in
*Messages* buffer, so it’s easy to check to see that they’re there.
Great! We successfully compiled and loaded our plugin into Emacs, without
writing a lick of Elisp (except for the
(require 'ecaml-bf)). Next time we’ll
start working on our interpreter. Stay tuned!
I wrote much of an earlier version of Ecaml2 (e.g., the C stubs and low-level OCaml interface) during my internship at Jane Street in the summer of 2016 under the tremendous guidance of my mentor Stephen Weeks. It’s a great place to work, and I recommend anyone interested in their internship program to definitely check it out and/or apply.
Of course, it has since been much expanded, rewritten, and improved upon by people much more experienced than me.