Elixir and Distel
Distel: Emacs talking Erlixir
alchemist.el is a Swiss army knife for working with Elixir from the comfort of Emacs. It can run mix tasks, look up Elixir source documentation, autocomplete, ... etc. Alchemist is awesome.
But, here’s the rub: often alchemist has to boot an Erlang VM to evaluate a single expression. Running tests boots a VM, looking up a function’s source doc boots a VM, each prefix autocompletion boots a VM. This is slow, and if you want the VM to load the code you’re actively developing you better not have any compile-time errors.
Plus, with features like hot code reloading, supervision trees, and remote shells, Erlang loves to be manipulated live. Stuart Sierra, a Clojurian, said:
One of the great pleasures of working with a dynamic language is being able to build a system while simultaneously interacting with it.
Enter distel. Distel implements the Erlang distribution protocol in ELisp. More on the nitty gritty of that later, but long story short: distel allows Emacs to pass as an Erlang node; a gnu in Erlang’s clothing (?!). Instead of booting a new Erlang VM to evaluate an expression, the functions can be called on a running node via RPC. Start your application on the remote node with an autoreloader and you have a long-lived development session.
The rest of this post is a brief overview of how to use distel from an Elixir perspective.
Connecting Distel
I’ll leave the installation up to you, clever reader.
For programming with distel, the Emacs info pages are surprisingly mediocre. Instead, see the included Gorrie 02.
First, you need a node running distributed Erlang; booting an Erlang VM running an Elixir console will do:
iex --sname emacs [-S mix]
The first time you run any distel command you’ll be prompted for
the name of the Erlang node to connect to. If you used the short
name emacs
as above, the node name is just emacs
If you’re having issues, make sure that your cookie matches.
RPC against an Erlang node
Distel loads an RPC library, rex, into the remote node. On the
Emacs side, distel provides erl-send-rpc
as a helper to call into
rex. The function signature, (erl-send-rpc NODE MODULE FUNCTION
ARGS)
, is reminiscient to Erlang RPC and apply calls and works
similarly.
After handling the call, the target node sends a message
back to Emacs wrapped in an rex
tuple, which can be caught with
erl-receive
. Look at the function docs for erl-receive
-- between
pattern matching and variable binding it does quite a bit.
Putting it all together, here’s a function that will call
Enum.count/1
with the arg LIST
on the remote node:
(defun elixir-enum-count (node list) "Use NODE to call `Enum.count(LIST)'" (erl-spawn (erl-send-rpc node 'Elixir.Enum 'count (list expr)) (erl-receive () ((['rex results] (message "results: %S" results))))))
Notice that the module, Enum
is referenced using its absolute
name: Elixir.Enum
. You must use the absolute name when
referencing an Elixir module.
Another catch: the result of erl-receive
isn’t returned to the
calling context; you must use the continuation passing-style to
handle results. i.e. callbacks, ahoy! Here’s the previous example
rewritten with the result being passed to the callback function.
(defun proximel-enum-count (node expr &optional callback) "Use NODE to generate a list of completions for EXPR. Optionally call CALLBACK with the completions." (erl-spawn (erl-send-rpc node 'Elixir.Proximel 'expand (list expr)) (erl-receive (callback) ((['rex results] (progn (message "results: %S" results) (if callback (funcall callback results))))))))
All iterations of proximel-enum-count
accept the Erlang NODE
as
the first argument. The current value of NODE
can be fetched with
the function erl-target-node
. This makes it easy to define
interactive functions that accept a node:
(defun elixir-enum-count (node list) "Use NODE to call `Enum.count(LIST)'" (interactive (list (erl-target-node) (read-minbuffer "Enter list: "))) (erl-spawn (erl-send-rpc node 'Elixir.Enum 'count (list expr)) (erl-receive () ((['rex results] (message "results: %S" results))))))
Demos: Proximel
I put together a basic repo, proximel, which demos loading beam files compiled from Elixir source into a remote node, and basic Elixir autocompletion.
In a future post I’ll dissect Elixir autocompletion over distel with company-mode.