A Challenge: De-‘dash’ and De-‘s’ an Elisp snippet
Down below you see an Emacs Lisp
code which uses dash
, and s
libraries.
You are tasked with de-‘dash’-ing and de-‘s’-ing this code so that it no longer relies on the dash
🔗 and s
🔗 libraries.
How would go about it?
el-search-demo.el
: De-‘dash’ and De-‘s’ this Elisp snippet
(require 'dash) (require 's) (eval-when-compile (defvar esd-bg-colors '("#fed9df" "#70f5ff" "#fed6f4" "#80ff26" "#cde5ff" "#f5e600" "#e9ddff" "#28ffcb" "#fedbc7"))) (defmacro esd-define-faces () `(progn ,@(->> esd-bg-colors (-map-indexed (lambda (i x) (list (format "esd-bg-face-%02d" (1+ i)) `(:background ,x :inherit esd-bg-base-face)))) (--map `(defface ,(intern (nth 0 it)) '((t ,(nth 1 it))) ,(s-join " " (mapcar #'capitalize (s-split "-" (nth 0 it)))) :group 'esd-hi-lock-faces))))) (esd-define-faces) (provide 'el-search-demo)
The above Elisp
code is from my own .emacs
.
Given a set of background colors (= esd-bg-colors
), it defines a set of faces (= esd-bg-face-01
, esd-bg-face-02
, …) for ad-hoc highlighting with hi-lock-mode
.
The above code uses
->>
--map
-map-indexed
from the dash
library
s-split
s-join
from the s
library.
A Response to the above Challenge: I will use query-replace
and a set of keyboard macros
that re-arranges sexps
… Hmmm … Wait a minute!
A possible answer to the above challenge of de-‘dash’-ing and de-‘s’-ing is you will replace the dash
and s
APIs with their native equivalent APIs.
Non-native APIs | Native Equivalents |
---|---|
->> |
thread-last |
--map |
seq-map |
-map-indexed |
seq-map-indexed |
s-split |
string-split |
s-join |
string-join |
How cumbersome do you think such a replacement will be?
Hard? Very hard?
Error prone?
It is a waste of time … just stick to dash
and s
because they are already available as part of GNU ELPA.
How long do you think it will take?
An hour, a few hours, a day …?
Well actually … all this re-factoring can be done with a flick of a switch using el-search
and el-search-refactor
.
This article will explore what el-search
🔗 does, and how el-search-refactor
🔗 extends el-search
in useful ways.
About el-search
package
el-search
🔗 is
an expression based interactive search for Emacs Lisp
and goes on to say
This package implements an expression based interactive search tool for Emacs Lisp files and buffers. The pattern language used is a superset of pcase
patterns.
“el-search” is multi file/buffer search capable. It is designed to be fast and easy to use. It offers an occur-like overview of matches and can do query-replace based on the same set of patterns. All searches are added to a history and can be resumed or restarted later. Finally, it allows you to define your own kinds of search patterns and your own multi-search commands.
In other words, think of el-search
as an isearch
, an occur
or a query-replace
equivalent for Lisp
sexps. You can read more about the package in its README
🔗 or in in its Commentary
🔗 .
This article exclusively focuses on the query-replace
functionality of el-search
. In regard to this functionality, the el-search
commentary says the following:
Advanced usage: Replacement rules for semi-automatic code rewriting
When you want to rewrite larger code parts programmatically, it can often be useful to define a dedicated pattern type to perform the replacement. Here is an example:
You heard that in many situations, dolist
is faster than an equivalent mapc
. You use mapc
quite often in your code and want to query-replace many occurrences in your stuff. Instead of using an ad hoc replacing rule, it’s cleaner to define a dedicated named pattern type using el-search-defpattern
. Make this pattern accept an argument and use it to bind a replacement expression to a variable you specify. In query-replace, specify that variable as replacement expression.
In our case, the pattern could look like this:
(el-search-defpattern el-search-mapc->dolist (new) (let ((var (make-symbol "var")) (body (make-symbol "body")) (list (make-symbol "list"))) `(and `(mapc (lambda (,,var) . ,,body) ,,list) (let ,new `(dolist (,,var ,,list) . ,,body)))))
The first condition in the and
performs the matching and binds the essential parts of the mapc
form to helper variables. The second, the let
, part, binds the specified variable NEW to the rewritten expression – in our case, a dolist
form is constructed with the remembered code parts filled in.
Now after this preparatory work, for el-search-query-replace
you can simply specify (literally!) the following rule:
(el-search-mapc->dolist repl) -> repl
About el-search-refactor
el-search
provides a solid foundation for re-writing code; It is an un-assembled BMW. It could be an “ultimate driving machine” but it has no comfortable seats, a sheath for the steering wheel or a protective roof.
This is where el-search-refactor
🔗 comes in to a picture. It provides comfortable seats to and a housing over the BMW.
The user option esr-replacement-rules
is the seating; the esr-rewrite:...
commands and esr-transform:...
functions or the leather sheath for the steering wheel. Since the rules are pre-configured by an expert (Ahem!) it is safe, reliable and just works.
In order to appreciate the previous paragraph, you need to take the BMW (with accessories) for a test drive.
Test drive el-search-refactor
Add the following snippet to your init.el
.
This snippet arranges for M-s e
to load el-search
on demand.
use-package
directive for el-search
(use-package el-search :defer t :functions (el-search-make-matcher) :bind (("M-s e" . (lambda () (interactive) (global-unset-key (kbd "M-s e")) (el-search-install-bindings-under-prefix [(meta ?s) ?e]) (let ((emacs-lisp-mode-map lisp-interaction-mode-map)) (el-search-install-bindings-under-prefix [(meta ?s) ?e])) (when (memq major-mode '(emacs-lisp-mode lisp-interaction-mode)) (let* ((kv (this-command-keys-vector)) (_key (key-description kv))) (setq unread-command-events (mapcar (lambda (ev) (cons t ev)) (listify-key-sequence kv)))))))) :config (use-package el-search-x))
Add the following snippet to your init.el
.
This snippet loads el-search-refactor
.
use-package
directive for el-search-refactor
(use-package el-search-refactor)
el-search-refactor
defines the following commands
Commands defined by el-search-refactor
Now,
C-x C-f el-search-demo.el
- Run the above marked commands with
M-x esr-rewrite:--map
,M-x esr-rewrite:-map-indexed
and,M-x esr-rewrite:s-split-or-s-join
- Do
C-M-%
of->>
withthread-last
.
Now you will be left with following code.
el-search-demo.el
: Before and After Re-factoring with el-search-refactor
What does el-search-refactor
do?
Now you would have guessed what el-search-refactor
does:
el-search-refactor
is a library that provides a set of commands to re-write (think re-factor) your Emacs Lisp source files. These “re-write” commands are automatically derived from a set of pre-configured pcase
and el-search-defpattern
rules.
el-search-refactor
comes with limited set of re-write rules, just enough to illustrate the re-writing features of el-search
.
A Visual Guide to el-search-refactor
el-search-refactor
defines the following commands
Commands defined by el-search-refactor
which are derived from the following rules
Rules defined by el-search-refactor
(= esr-replacement-rules
)
Note, that there are two types of rules in el-search-refactor
- Pcase Rule
-
This rule is a triplet of a tag, a `pcase’ pattern and a `pcase’ replacement expression
If you know how to use a
pcase
macro, you can immediately recognize thepcase
-ness of thepattern
andreplacement
forms. - El Search Defpattern
-
This rule is the name of a
el-search-defpattern
.The
el-search-mapc->dolist
alluded in to theel-search
commentary is a good example of this rule.
A Textual Guide to el-search-refactor
el-search-refactor
defines the following commands
- Commands for use with
el-search-demo
-
- esr-rewrite:–map
- esr-rewrite:-map-indexed
- esr-rewrite:s-split-or-s-join
- Command for transforming
mapc
todolist
-
- esr-rewrite:mapc->dolist
- Comands for transforming “conventional” key bindings in to a
use-package
-specific form -
- esr-rewrite:define-key->bind-key
- esr-rewrite:define-key->key-and-command
- esr-rewrite:global-set-key->bind-key
- esr-rewrite:global-unset-key->bind-key
- esr-rewrite:legacy-keymap-definition->bind-keys
which are defined by the following rules (= esr-replacement-rules
)
Entries in esr-replacement-rules
( ;; Rules for use with =el-search-demo= ;; These rules are a triplet of a tag, a `pcase' pattern and a `pcase' ;; replacement expression ( --map `(--map ,tag) `(seq-map (lambda (it) ,tag)) ) ( -map-indexed `(-map-indexed ,(append '(lambda) `((,i ,elt)) body)) `(seq-map-indexed (lambda (,elt ,i) ,@body)) ) ( s-split-or-s-join `(,(and (or 's-split 's-join) f) ,x ,y) `( ,(alist-get f `((s-join . string-join) (s-split . string-split))) ,y ,x) ) ;; Rule for transforming =mapc= to =dolist= ;; This is an `el-search-defpattern'. ;; This is a verbatim copy of `el-search-mapc->dolist' save for the name. mapc->dolist ;; Rules for transforming “conventional” key bindings in to ;; `use-package=-specific' compatible forms ;; These are also `el-search-defpattern'-s define-key->key-and-command define-key->bind-key global-set-key->bind-key global-unset-key->bind-key legacy-keymap-definition->bind-keys )
Conclusion
el-search
is an awesome library for re-writing / revising your Emacs Lisp
code.
The complexity of the transformation that a user can achieve with el-search
is unlimited save for their own imagination and skill with Emacs Lisp
.
Stated another way, el-search
is an advanced code re-writing tool, and its effective usage places extra-ordinary demands on its user.
A user of el-search
library MUST
- be fluent with
pcase
🔗 and friends, specifically an ability to define custompcase
patterns usingpcase-defmacro
🔗 - be fluent manipulating nested
comma
, andbackquote
-s as it occurs inpcase
expressions 🔗 and regularEmacs Lisp
code 🔗
With no pre-configured re-writing rules, el-search
can only be used for ad-hoc rewriting of Elisp
form, and that too only by expert users. It is in this context el-search-refactor
comes in to picture. It envisions a wrapper around el-search
whereby
- experts can build and collect “generally useful” set of code re-writing rules
- ordinary users can use and provide feedback on existing set of code re-writing rules, or propose new rules
This article is intended as a “Heads Up!” accessible introduction to el-search
, and covers a much limited ground. I invite readers of this article
- to contribute new rules to
esr-replacement-rules
-
dive in to
el-search
, and come up with more libraries that extend it.el-search-refactor
is my own humble contribution in this direction.
HAPPY HACKING