(or emacs irrelevant)

Elisp newbie-style

I've been cleaning up my .emacs lately, with the intention to put my whole config on Github. Today, I'll show you a useful function for describing the current buffer's key bindings. The function will generate something like this (with all letters, of course):

;; (global-set-key (kbd "a") 'self-insert-command)
;; ...
;; (global-set-key (kbd "C-b") 'backward-char)
;; ...
;; (global-set-key (kbd "M-c") 'subword-capitalize)
;; ...
;; (global-set-key (kbd "C-M-d") 'down-list)
;; ...
;; (global-set-key (kbd "η") 'save-and-switch-buffer)
;; ...
;; (global-set-key (kbd "C-θ") 'ins-single-quotes)
;; ...
;; (global-set-key (kbd "M-ι") 'nil)
;; ...
;; (global-set-key (kbd "C-M-κ") 'nil)

First, I'll show you how I wrote it down as a newbie, and then today's corrections with some remarks.

Old style

Please don't try this at home:

;;;###autoload
(defun keys-latin ()
  (loop for c from ?a to ?z
     collect (string c)))

;;;###autoload
(defun keys-greek ()
  (loop for c from  to 
     collect (string c)))

(require 'dash)
;;;###autoload
(defun keys-describe-prefix (letters prefix)
  (->> letters
    (mapcar (lambda (letter) (concat prefix letter)))
    (mapcar (lambda (key)
              (cons key
                    (prin1-to-string (key-binding (kbd key))))))
    (mapcar (lambda (binding)
              (concat
               ";; (global-set-key (kbd \""
               (car binding)
               "\") '"
               (cdr binding)
               ")\n")))
    (apply #'concat)))

;;;###autoload
(defun keys-describe-prefixes ()
  (interactive)
  (with-output-to-temp-buffer "*Bindings*"
    (mapcar
     (lambda (f-letters)
       (mapcar (lambda (prefix)
                 (princ (keys-describe-prefix f-letters prefix))
                 (princ "\n\n"))
               '("" "C-" "M-" "C-M-")))
     (list (keys-latin) (keys-greek)))))

Corrections

Redundant autoloads

Since the entry point of the whole thing is keys-describe-prefixes, only it needs to be autoloaded. Once an autoloaded function is called, it will load the whole buffer. So if e.g. keys-latin is not being used anywhere else outside this file, it doesn't need an autoload.

Redundant functions

keys-latin and keys-greek are actually very small and not used anywhere else. It might be better to just inline them into keys-describe-prefixes.

Redundant libraries

Here, dash is required just for the ->> macro, which can actually be obtained from a core library subr-x as thread-last.

But even then, it's just better to unwind the whole thing. After that, it becomes clear that the three consecutive mapcars could be folded into a single mapcar with the help of a let binding.

After the fold, it starts to look silly, since I'm consing just to take a car and cdr later:

(defun keys-describe-prefix (letters prefix)
  (apply #'concat
         (mapcar
          (lambda (letter)
            (let* ((key (concat prefix letter))
                   (binding
                    (cons key
                          (prin1-to-string
                           (key-binding (kbd key))))))
              (concat
               ";; (global-set-key (kbd \""
               (car binding)
               "\") '"
               (cdr binding)
               ")\n")))
          letters)))

Here's a simplification, removing binding:

(defun keys-describe-prefix (letters prefix)
  (apply #'concat
         (mapcar
          (lambda (letter)
            (let ((key (concat prefix letter)))
              (concat
               ";; (global-set-key (kbd \""
               key
               "\") '"
               (prin1-to-string
                (key-binding (kbd key)))
               ")\n")))
          letters)))

I guess that I didn't know about format function back then, and the fact that "%S" key is equivalent to prin1-to-string:

(defun keys-describe-prefix (letters prefix)
  (apply #'concat
         (mapcar
          (lambda (letter)
            (let ((key (concat prefix letter)))
              (format ";; (global-set-key (kbd \"%s\") '%S)\n"
                      key
                      (key-binding (kbd key)))))
          letters)))

Next, the combination (apply #'concat (mapcar ...)) is already implemented in C as mapconcat:

(defun keys-describe-prefix (letters prefix)
  (mapconcat
   (lambda (letter)
     (let ((key (concat prefix letter)))
       (format ";; (global-set-key (kbd \"%s\") '%S)"
               key
               (key-binding (kbd key)))))
   letters
   "\n"))

In the end, keys-describe-prefix turned out to be so small that I could just inline it into keys-describe-prefixes.

Final version

Note that here I also replaced mapcar with dolist:

;;;###autoload
(defun keys-describe-prefixes ()
  (interactive)
  (with-output-to-temp-buffer "*Bindings*"
    (dolist (letter-group (list
                           (cl-loop for c from ?a to ?z
                                    collect (string c))
                           (cl-loop for c from  to 
                                    collect (string c))))
      (dolist (prefix '("" "C-" "M-" "C-M-"))
        (princ (mapconcat
                (lambda (letter)
                  (let ((key (concat prefix letter)))
                    (format ";; (global-set-key (kbd \"%s\") '%S)"
                            key
                            (key-binding (kbd key)))))
                letter-group
                "\n"))
        (princ "\n\n")))))

I hope that this sort of analysis can be useful for people starting to learn Elisp. And if you have corrections for the final version, do let me know, I don't mind being schooled, as long as I get better in the end.

The output of keys-describe-prefixes can be used to learn some bindings that you didn't know about, and also as a template to redefine some bindings that you don't need.