Moldable Emacs: making molds a little easier to write
Too long; didn't read
Molds definition :given
and :then
don't accept lambdas anymore:
you want to declare a function with :fn
instead. This small
(breaking) change will bring some benefits. If you have already
defined your molds, here you will find how to move them to the new
format.
The problem
I am pretty proud of moldable-emacs. Lately I am creating molds for making my work simpler. For example, just the other day I shared with my colleagues how I collect production errors with a mold. I pull the errors from our logging system and then I analyse them with another mold. The idea is to bring that report into my editor. Soon (I hope) Emacs will point to me which file I have to fix to get rid of a given error!
By the way, a reminder of the power of moldable development: just showing my little (rough) analysis prompted a colleague to solve an issue with a feature we are releasing! Again a custom view can save the day (and time).
When molds gather data from other systems, they often block Emacs. So, I devised a little pattern to use the great async.el to keep my Emacs usable. Anytime a mold needs slow-to-get data, I start a process that does that in the background. In the meanwhile Emacs fetches the data, my mold displays a message like "Loading some data" in the output buffer.
This pattern works, but it is tiring to code and easy to get wrong. And since the molds I make for work will ping all sort of services for data, I surely need a better solution.
Would it not be amazing to just tag mold sections to say run this async?
It is a problem indeed
This is a more general issue. Each bit of a mold definition could carry metadata useful for running it. My first design of molds is not declarative enough. I decided to use a property list to make sure that molds are data that Emacs can interpret (and run). I now realize I left something out though.
This is how a mold currently looks like:
(me/register-mold :key "Query" :given (lambda () 't) :then (lambda () (let ((self (ignore-errors (save-excursion (goto-char (point-min)) (eval `',(read (current-buffer)))))) (sexps (call-interactively 'eval-expression)) (buffer (get-buffer-create "m/tree"))) (with-current-buffer buffer (erase-buffer) (setq-local self sexps) (me/print-to-buffer sexps buffer) (emacs-lisp-mode) buffer) buffer)))
(This mold is a nice utility: you write an Elisp expression that runs in the current buffer and outputs the results in a new buffer.)
You can see that the :given
and :then
bits are lambdas. That is a
blocker. When I get the :given
of the "Query" mold, I have to run it.
The more I think about it, the more it seems that :given
could be
other things than a function. For example, it could be a regexp! We
could interpret that to activate the mold anytime that regexp is found
in the buffer. Or it could be a function with a flat to run it asynchronously!
Do you see? I feel that in my first design I missed just an important little out that could mean a lot later on.
And there is a solution
Time to improve things! Let me show how the new "Query" mold looks like:
(me/register-mold-1 :key "Query" :given (:fn 't) :then (:fn (let ((self (ignore-errors (save-excursion (goto-char (point-min)) (eval `',(read (current-buffer)))))) (sexps (call-interactively 'eval-expression))) (with-current-buffer buffername (emacs-lisp-mode) (erase-buffer) (setq-local self sexps) (me/print-to-buffer sexps)))))
The lambdas are gone! The :fn
stands for lambda here. The
me-mold-1
function (which runs molds) will interpret that as
something to run.
So we can imagine (didn't finish to implement that yet though) that to
make the :then
clause run my async pattern, we could define "Query"
like this:
(me/register-mold-1 :key "Query" :given (:fn 't) :then (:fn ... :async t))
It would be clean, no? Also, now that I think of it, I could make so that users can define their own custom interpretation of tags. A good way to separate concerns, I guess (Aspect Oriented Programming, a remake).
Note another thing: you don't need to create anymore the output buffer
manually. Experience showed me that is a poor investment of time. You
can still define a custom name by defining a :buffername
property
for the mold.
Another little feature I introduce is a :let
keyword. With that you
can define bindings available to both :given
and :then
clauses.
This increase code reuse for some molds. Later I want to make sure
that these bindings are evaluated only once (_for now is evaluated
twice_), so to spare computations.
The steps to refactor molds for the new format are the following.
- replace
lambda
in:given
and:then
with:fn
- use
buffername
to use the mold's output buffer in the:then
clause - put common variables between
:given
and:then
in:let
clause - stop returning buffer at the end of the
:then
clause
If you want to use the new format already, just use me/mold-1
instead of me/mold
to run molds AND put the following setting in your init:
(setq me/files-with-molds (list "<moldable-emacs>/molds/contrib-1.el" "<moldable-emacs>/molds/core-1.el"))
Where <moldable-emacs>
stands for your path to the mode. This will
load and use the refactored molds.
All in all, a small breaking change! I shall post soon about the improvements this allows!
Conclusion
Now a few interesting doors are open for moldable-emacs to evolve. Please let me know if you have any feedback!
Happy molding!