How to programmatically re-write your Elisp code with ‘el-search’ and ‘el-search-refactor’ (Or) How to De-‘dash’, De-‘s’ and De-‘f’ your Elisp Libraries


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

  1. ->>
  2. --map
  3. -map-indexed

from the dash library

  1. s-split
  2. 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

esr-rewrite.png

Now,

  1. C-x C-f el-search-demo.el
  2. 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
  3. Do C-M-% of ->> with thread-last.

Now you will be left with following code.

el-search-demo.el: Before and After Re-factoring with el-search-refactor

el-search-demo-before-and-after.png

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

esr-rewrite.png

which are derived from the following rules

Rules defined by el-search-refactor (= esr-replacement-rules)

esr-replacement-rules.png

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 the pcase-ness of the pattern and replacement forms.

El Search Defpattern
This rule is the name of a el-search-defpattern.

The el-search-mapc->dolist alluded in to the el-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 to dolist
  • 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 custom pcase patterns using pcase-defmacro 🔗
  • be fluent manipulating nested comma, and backquote-s as it occurs in pcase expressions 🔗 and regular Emacs 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

Categories gnu

Leave a comment

search previous next tag category expand menu location phone mail time cart zoom edit close