Subject: [PATCH] New macro 'compf' for composing functions

From: Eshel Yaron <[email-masked]>
Date: Wed, 19 Feb 2025 14:52:11 +0100

Tags: patch

This patch adds a new macro 'compf' that streamlines the common pattern
of function composition.

Namely, instead of (lambda (x) (foo (bar (baz x)))), with this macro we
can write (compf foo bar baz), which expands to exactly the same form.

This is similar in essence to the "compose" function proposed in
https://lists.gnu.org/archive/html/emacs-devel/2021-02/msg01138.html
with the main difference that compf is a macro, not a function, so it
incurs no runtime performance penalty.

There are many occurrences of function composition in the Emacs code
base alone that can be simplified with this macro.  For example:

- In lisp/calc/calc-mode.el:
  (lambda (v) (symbol-value (car v)))       => (compf symbol-value car)
- In lisp/dired-aux.el:
  (lambda (dir) (not (file-remote-p dir)))  => (compf not file-remote-p)
- In lisp/env.el:
  (lambda (var) (getenv (upcase var)))      => (compf getenv upcase)
- In lisp/mail/smtpmail.el:
  (lambda (s) (intern (downcase s)))        => (compf intern downcase)
- In lisp/net/tramp-container.el:
  (lambda (line) (car (split-string line))) => (compf car split-line)
- ...

Best,

Eshel

>From fffadc1a6cb04b925d136859db66e4b706563651 Mon Sep 17 00:00:00 2001
From: Eshel Yaron <[email-masked]>
Date: Wed, 19 Feb 2025 12:58:40 +0100
Subject: [PATCH] New macro 'compf' for composing functions

* lisp/subr.el (compf): New macro.
---
 lisp/subr.el | 33 +++++++++++++++++++++++++++++++++
 1 file changed, 33 insertions(+)

diff --git a/lisp/subr.el b/lisp/subr.el
index e9b49ae5376..a76dad8ea82 100644
--- a/lisp/subr.el
+++ b/lisp/subr.el
@@ -7678,4 +7678,37 @@ internal--c-header-file-path
                   base
                 (append base (list (expand-file-name arch "/usr/include"))))))))))
 
+(defmacro compf (&rest funs)
+  "Expand to the function composition of FUNS, outermost function first.
+
+For example, (compf car cdr) expands to (lambda (x) (car (cdr x))),
+which does the same as `cadr'.
+
+FUNS may contain symbols which refer to functions, such as `car' and
+`cdr' in the example above, and it can also contain `lambda' functions
+and other forms which evaluate to function values.  To refer to a local
+variable VAR that is bound to a function, wrap VAR in a vector, as in:
+
+  (let ((foo (lambda (...) ...)
+    (compf ignore [foo] always))
+
+If FUNS is empty, expand to `identity'."
+  (cond
+   ((null funs) '#'identity)
+   ((length= funs 1)
+    (let ((fun (car funs)))
+      (cond
+       ((symbolp fun) `#',fun)          ; Function name.
+       ((vectorp fun) (aref fun 0))     ; Local variable reference.
+       (t fun))))                       ; `lambda' and other forms.
+   (t
+    (let ((arg 'x))
+      (dolist (fun (reverse funs))
+        (setq arg
+              (cond
+               ((symbolp fun) `(,fun ,arg))
+               ((vectorp fun) `(funcall ,(aref fun 0) ,arg))
+               (t `(funcall ,fun ,arg)))))
+      `(lambda (x) ,arg)))))
+
 ;;; subr.el ends here
-- 
2.46.2