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
lambdain:givenand:thenwith:fn - use
buffernameto use the mold's output buffer in the:thenclause - put common variables between
:givenand:thenin:letclause - stop returning buffer at the end of the
:thenclause
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!