Refactoring Emacs Lisp with ChatGPT

Why?

Look… If you haven’t been living under a rock, you’ve probably noticed that ChatGPT is a hot topic on the internet.

Rightfully so, if you ask me. This thing is amazing. I got no words.

I am not going to explain it because I do not feel like I am the right person to do so. Just visit openai.com and see for yourself. However here are a few words from the devs:

ChatGPT is fine-tuned from a model in the GPT-3.5 series, which finished training in early 2022.

Since I am no wiz kid, when it come to write Emacs lisp I needed to give it a shot, right?

What?

I of course tried out a lot of things, both code-related and not. However, I am only going to talk about a few functions in my Emacs configuration that I – or rather, ChatGPT – refactored.

For now, I am going to list the before and after versions for lack of more time. I just did not want to miss the opportunity to write a post for posterity (mostly for myself).

Macro to require my custom modules

(defmacro timu-func-require-modules (&rest modules)
  "Load MODULES, if they are not already loaded and are installed.
Skips any modules that are not found.

Displays a message in the minibuffer for any modules that are not found.
Displays a message in the minibuffer for any modules that are already loaded.

Credit: https://chat.openai.com/chat."
  `(progn
     ,@(mapcar (lambda (module)
                 `(when (not (featurep ',module))
                    (if (locate-library ,(symbol-name module))
                        (progn
                          (require ',module)
                          (message "Loaded module '%s'" ',module))
                      (message "Module '%s' not found" ',module))))
               modules)))

With this my custom functions to load the modules at startup change from this:

(defun timu-func-load-modules ()
  "Load all custom the modules using `require'.
The order in which the modules are loaded is important."
  (interactive)
  (progn
    (require 'timu-defaults)
    (require 'timu-personal)
    (require 'timu-evil)
    (require 'timu-ui)
    (require 'timu-dired)
    (pcase system-type
      ((or'darwin 'gnu/linux)
       (require 'timu-pdf)))
    (require 'timu-org)
    (require 'timu-nav)
    (require 'timu-minibuffer)
    (require 'timu-transients)
    (require 'timu-editor)
    (require 'timu-shell)
    (pcase system-type
      ((or'darwin 'gnu/linux)
       (require 'timu-git)))
    (if timu-defaults-wsl-p
        (message "No mu4e for wsl")
      (pcase system-type
        ((or'darwin 'gnu/linux)
         (require 'timu-mu4e))))
    (require 'timu-elfeed)
    (pcase system-type
      ((or'darwin 'gnu/linux)
       (require 'timu-prog)))
    (pcase system-type
      ((or'darwin 'gnu/linux)
       (require 'timu-latex)))
    (require 'timu-major-modes)
    (pcase system-type
      ((or'darwin 'gnu/linux)
       (require 'timu-work)))
    (require 'timu-fun)
    (require 'timu-map)
    (require 'timu-keys)
    (require 'timu-scratch)))

to this:

(defun timu-func-load-modules ()
  "Load all custom the modules using `require'.
  The order in which the modules are loaded is important."
  (interactive)
  (timu-func-require-modules
   timu-defaults
   timu-personal
   timu-evil
   timu-ui
   timu-dired
   timu-org
   timu-nav
   timu-minibuffer
   timu-major-modes
   timu-transients
   timu-editor
   timu-shell
   timu-fun
   timu-map
   timu-keys
   timu-scratch)
  (pcase system-type
    ((or 'darwin 'gnu/linux)
     (timu-func-require-modules
      timu-pdf
      timu-git
      timu-mu4e
      timu-elfeed
      timu-prog
      timu-latex
      timu-work))))

As a little bonus, using the macro also reduced the Emacs startup time by about 1.5 seconds.

Function to attach files in mu4e with less prompts

(defun timu-func-mu4e-attach-file ()
  "Attach a file to an email.
Use the built-in function `mml-attach-file'."
  (interactive)
  (let ((default-directory "~/"))
    (let ((file (read-file-name "Select a file to attach: ")))
      (mml-attach-file (expand-file-name file)))))

Heavy refactoring of some functions

Since I am being lazy for this part, I am going to dump a diff here.

This included:

  • Using let in a function instead of defining variables separately
  • Using lamdas inside of functions instead of several helper functions
  • And much more…

Listen… I know that some of it doesn’t make sense. Arguably, it does make readability worse. Still, I needed to play around, and this was the result.

In case you are curious, you can find more details in the commits here.

Heavy refactor functions using chatGPT

1 file changed, 39 insertions(+), 52 deletions(-)
libraries/timu-func.el | 91 ++++++++++++++++++++++----------------------------

modified   libraries/timu-func.el
@@ -1000,32 +1000,34 @@ Triggered by a custom macOS Quick Action with keybinding."
 ;;; ORG REFILING SUBTREE TO A STANDALONE FILE
 (defun timu-func-org-get-subtree-tags (&optional props)
   "Given PROPS, from a call to `org-entry-properties', return a list of tags."
-  (unless props
-    (setq props (org-entry-properties)))
-  (let ((tag-label (if timu-func-org-get-subtree-tags-inherited "ALLTAGS" "TAGS")))
-    (-some->> props
-      (assoc tag-label)
-      cdr
-      substring-no-properties
-      (s-split ":")
-      (--filter (not (cl-equalp "" it))))))
+  (let ((tags-inherited t))
+    (unless props
+      (setq props (org-entry-properties)))
+    (let ((tag-label (if tags-inherited "ALLTAGS" "TAGS")))
+      (-some->> props
+        (assoc tag-label)
+        cdr
+        substring-no-properties
+        (s-split ":")
+        (--filter (not (cl-equalp "" it)))))))

 (defun timu-func-org-get-subtree-properties (attributes)
   "Return a list of tuples of a subtrees ATTRIBUTES where the keys are strings."
-
-  (defun timu-func-symbol-upcase-p (sym)
-    (let ((case-fold-search nil))
-      (string-match-p "^:[A-Z]+$" (symbol-name sym))))
-
-  (defun timu-func-convert-tuple (tup)
-    (let ((key (cl-first tup))
-          (val (cl-second tup)))
-      (list (substring (symbol-name key) 1) val)))
-
-  (->> attributes
-       (-partition 2) ; Convert plist to list of tuples
-       (--filter (timu-func-symbol-upcase-p (cl-first it))) ; Remove lowercase tuples
-       (-map 'timu-func-convert-tuple)))
+  (let ((timu-func-symbol-upcase-p
+         (lambda (sym)
+           (let ((case-fold-search nil))
+             (string-match-p "^:[A-Z]+$" (symbol-name sym)))))
+        (timu-func-convert-tuple
+         (lambda (tup)
+           (let ((key (cl-first tup))
+                 (val (cl-second tup)))
+             (list (substring (symbol-name key) 1) val)))))
+    (->> attributes
+         ;; Convert plist to list of tuples
+         (-partition 2)
+         ;; Remove lowercase tuples
+         (--filter (funcall timu-func-symbol-upcase-p (cl-first it)))
+         (-map timu-func-convert-tuple))))

 (defun timu-func-org-get-subtree-content (attributes)
   "Return the contents and ATTRIBUTES of the current subtree as a string."
@@ -1060,11 +1062,6 @@ region list of the start and end of the subtree."
             :properties (timu-func-org-get-subtree-properties attrs)
             :body       (timu-func-org-get-subtree-content attrs)))))

-(defvar timu-func-org-get-subtree-tags-inherited t
-  "Returns a subtree's tags, and all tags inherited.
-This is from tags specified in parents headlines or on the file itself.
-Defaults to true.")
-
 (defun timu-func-org-set-file-property (key value &optional spot)
   "Make sure file has a top-level, file-wide property.
 KEY is something like \"TITLE\" or \"FILETAGS\".
@@ -1134,34 +1131,24 @@ It attempts to move as many of the properties and features to the new file."
     (apply #'delete-region area)
     (timu-func-org-create-org-file filepath head body tags properties)))

-(defvar timu-func-org-refile-directly-show-after nil
-  "Show the destination afterwards when using `timu-func-org-refile-directly'.
-This is if this is set to t, otherwise, just do all in the background.")
-
-(defun timu-func-org-subtree-region ()
-  "Return a list of the start and end of a subtree."
-  (save-excursion
-    (list (progn (org-back-to-heading) (point))
-          (progn (org-end-of-subtree)  (point)))))
-
-(defun timu-func-org-refile-directly (file-dest)
+(defun timu-func-org-refile-directly (file-dest &optional show-after)
   "Move the current subtree to the end of FILE-DEST.
 If SHOW-AFTER is non-nil, show the destination window,
 otherwise, this destination buffer is not shown."
   (interactive "fDestination: ")
-
-  (defun timu-func-dump-it (file contents)
-    (find-file-other-window file-dest)
-    (goto-char (point-max))
-    (insert "\n" contents))
-
-  (save-excursion
-    (let* ((region (timu-func-org-subtree-region))
-           (contents (buffer-substring (cl-first region) (cl-second region))))
-      (apply #'kill-region region)
-      (if timu-func-org-refile-directly-show-after
-          (save-current-buffer (timu-func-dump-it file-dest contents))
-        (save-window-excursion (timu-func-dump-it file-dest contents))))))
+  (let ((dump-it (lambda (file contents)
+                             (find-file-other-window file-dest)
+                             (goto-char (point-max))
+                             (insert "\n" contents))))
+    (save-excursion
+      (let* ((region (save-excursion
+                       (list (progn (org-back-to-heading) (point))
+                             (progn (org-end-of-subtree)  (point)))))
+             (contents (buffer-substring (cl-first region) (cl-second region))))
+        (apply #'kill-region region)
+        (if show-after
+            (save-current-buffer (funcall dump-it file-dest contents))
+          (save-window-excursion (funcall dump-it file-dest contents)))))))


 ;;; OX-HUGO

Conclusion

Let’s close by some words by ChatGPT:

As a language model, my main purpose is to assist users in generating human-like text based on the input provided to me. This can be useful in a variety of applications, such as generating content for websites, assisting in the creation of documents or reports, and even providing personalized responses to messages or customer inquiries. I can also be useful in language translation tasks, summarizing long articles or documents, and providing information on a wide range of topics. Ultimately, my usefulness will depend on how I am trained and used by the developers and users who employ my services.

P.S.

About 95% of the prose here was reworded by ChatGPT.