Subject: [PATCH] seq.el: Add functions for mapping over subsequences
From: Okamsn <[email-masked]>
To: [email-masked], Nicolas Petton <[email-masked]>
Date: Sun, 26 Nov 2023 17:17:35 +0000
List-Id: <debbugs-submit.debbugs.gnu.org>

Hello,

The attached features work like `cl-maplist` and `cl-mapl`, applying 
functions to a sequence and to the remaining parts of a sequence.  For 
example, `(seq-mapsub #'identity [1 2 3])` returns `([1 2 3] [2 3] [3])`.

The patch adds a `seq-mapsub`, `seq-dosub`, and a `seq-doseqsub`, 
similar to `seq-map`, `seq-do`, and `seq-doseq`, respectively.

I was looking for an equivalent for vectors of `cl-maplist`, `cl-mapl`, 
and `cl-loop`'s `for VAR on LIST`, and think that these would be useful 
additions.

To get the sub-sequences, the code uses `seq-rest` and stops when the 
returned sub-sequence is empty according to `seq-empty-p`.  This is 
similar to how I would do it for a list, using `cdr` and `null`, but is 
that a good way to do it for arrays and other sequences?

Thank you.

From 2f07383470b90009c78b0459ee09fcc513e0e12f Mon Sep 17 00:00:00 2001
From: Earl Hyatt <[email-masked]>
Date: Sun, 26 Nov 2023 11:20:28 -0500
Subject: [PATCH] Create seq functions for mapping over subsequences.

These functions are similar to 'cl-maplist' and 'cl-mapl'.

* lisp/emacs-lisp/seq.el (seq-mapsub, seq-dosub, seq-doseqsub):
Add functions for mapping over sub-sequences, like in 'cl-maplist',
similar to the existing 'seq-map', 'seq-do', and 'seq-doseq'.

* test/lisp/emacs-lisp/seq-tests.el (test-seq-mapsub, test-seq-dosub)
(test-seq-doseqsub): Add tests for the above functions.

* doc/lispref/sequences.texi (Sequence Functions): Describe the
functions.
---
 doc/lispref/sequences.texi        | 27 ++++++++++++++++++++
 lisp/emacs-lisp/seq.el            | 41 +++++++++++++++++++++++++++++++
 test/lisp/emacs-lisp/seq-tests.el | 36 +++++++++++++++++++++++++++
 3 files changed, 104 insertions(+)

diff --git a/doc/lispref/sequences.texi b/doc/lispref/sequences.texi
index c9c6bb31350..e42f84be2b5 100644
--- a/doc/lispref/sequences.texi
+++ b/doc/lispref/sequences.texi
@@ -594,6 +594,15 @@ Sequence Functions
 @var{sequence}.
 @end defun
 
+@defun seq-dosub function sequence
+  This function applies @var{function} to @var{sequence} and its
+sub-sequences in turn (presumably for side effects), and returns
+@var{sequence}.  The first sequence used is @var{sequence}, the second
+sequence used is everything in @var{sequence} after the first element,
+the third sequence used is everything after the second element, and so
+on.
+@end defun
+
 @defun seq-map function sequence
   This function returns the result of applying @var{function} to each
 element of @var{sequence}.  The returned value is a list.
@@ -610,6 +619,19 @@ Sequence Functions
 @end example
 @end defun
 
+@defun seq-mapsub function sequence
+  This function returns the result of applying @var{function} to
+@var{sequence} and its sub-sequences, like in @code{seq-dosub}.  The
+returned value is a list.
+
+@example
+@group
+(seq-mapsub #'identity [1 2 3])
+@result{} ([1 2 3] [2 3] [3])
+@end group
+@end example
+@end defun
+
 @defun seq-map-indexed function sequence
   This function returns the result of applying @var{function} to each
 element of @var{sequence} and its index within @var{seq}.  The
@@ -1152,6 +1174,11 @@ Sequence Functions
 primarily useful for side-effects.
 @end defmac
 
+@defmac seq-doseqsub (var sequence) body@dots{}
+  This macro is like @code{seq-doseq}, except that it iterates
+over the sequence and its sub-sequences, like in @code{seq-mapsub}.
+@end defmac
+
 @anchor{seq-let}
 @defmac seq-let var-sequence val-sequence body@dots{}
 @cindex sequence destructuring
diff --git a/lisp/emacs-lisp/seq.el b/lisp/emacs-lisp/seq.el
index 346250c1d35..5b9eb29a3e2 100644
--- a/lisp/emacs-lisp/seq.el
+++ b/lisp/emacs-lisp/seq.el
@@ -235,6 +235,47 @@ seq-mapn
       (setq sequences (seq-map #'cdr sequences)))
     (nreverse result)))
 
+(cl-defgeneric seq-mapsub (function sequence)
+  "Return the result of applying FUNCTION to SEQUENCE and its sub-sequences.
+
+The first sequence is the entire sequence, the second sequence
+is everything after the first element, the third sequence
+is everything after the second element, and so on."
+  (let ((result nil))
+    (while (not (seq-empty-p sequence))
+      (push (funcall function sequence)
+            result)
+      (setq sequence (seq-rest sequence)))
+    (nreverse result)))
+
+(cl-defgeneric seq-dosub (function sequence)
+  "Loop over SEQUENCE and its sub-sequences.
+
+Presumably, FUNCTION has useful side effects.
+Return SEQUENCE.
+
+The first sequence is the entire sequence, the second sequence
+is everything after the first element, the third sequence
+is everything after the second element, and so on."
+  (let ((orig sequence))
+    (while (not (seq-empty-p sequence))
+      (funcall function sequence)
+      (setq sequence (seq-rest sequence)))
+    orig))
+
+(defmacro seq-doseqsub (spec &rest body)
+  "Loop over SEQUENCE, evaluating BODY with VAR bound to its sub-sequences.
+
+The first sequence is the entire sequence, the second sequence
+is everything after the first element, the third sequence
+is everything after the second element, and so on.
+
+\(fn (VAR SEQUENCE) BODY...)"
+  (declare (indent 1) (debug ((symbolp form &optional form) body)))
+  `(seq-dosub (lambda (,(car spec))
+                ,@body)
+              ,(cadr spec)))
+
 (cl-defgeneric seq-drop (sequence n)
   "Remove the first N elements of SEQUENCE and return the resulting sequence.
 The result is a sequence of the same type as SEQUENCE.
diff --git a/test/lisp/emacs-lisp/seq-tests.el b/test/lisp/emacs-lisp/seq-tests.el
index 71ff991c215..1c117d06974 100644
--- a/test/lisp/emacs-lisp/seq-tests.el
+++ b/test/lisp/emacs-lisp/seq-tests.el
@@ -121,6 +121,42 @@ test-seq-do-indexed
       (should (equal (seq-elt result 1) '(5 1)))
       (should (equal (seq-elt result 2) '(4 0))))))
 
+(ert-deftest test-seq-mapsub ()
+  (should (equal (seq-mapsub #'identity [1 2 3])
+                 '([1 2 3] [2 3] [3])))
+  (should (equal (seq-mapsub #'identity '(1 2 3))
+                 '((1 2 3) (2 3) (3)))))
+
+(ert-deftest test-seq-dosub ()
+  (should (equal (seq-dosub #'identity [1 2 3])
+                 [1 2 3]))
+  (should (equal (seq-dosub #'identity '(1 2 3))
+                 '(1 2 3)))
+  (should (equal (let ((res (list)))
+                   (seq-dosub (lambda (x)
+                                (push x res))
+                              [1 2 3])
+                   (nreverse res))
+                 '([1 2 3] [2 3] [3])))
+  (should (equal (let ((res (list)))
+                   (seq-dosub (lambda (x)
+                                (push x res))
+                              '(1 2 3))
+                   (nreverse res))
+                 '((1 2 3) (2 3) (3)))))
+
+(ert-deftest test-seq-doseqsub ()
+  (should (equal (let ((res (list)))
+                   (seq-doseqsub (x [1 2 3])
+                     (push x res))
+                   (nreverse res))
+                 '([1 2 3] [2 3] [3])))
+  (should (equal (let ((res (list)))
+                   (seq-doseqsub (x '(1 2 3))
+                     (push x res))
+                   (nreverse res))
+                 '((1 2 3) (2 3) (3)))))
+
 (ert-deftest test-seq-filter ()
   (with-test-sequences (seq '(6 7 8 9 10))
     (should (equal (seq-filter #'test-sequences-evenp seq) '(6 8 10)))
-- 
2.34.1