unofficial mirror of bug-gnu-emacs@gnu.org 
 help / color / mirror / code / Atom feed
* bug#68863: Add support for using setf with seq-subseq
@ 2024-02-01  3:31 Okamsn via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-02-04 18:33 ` bug#68863: [PATCH] " Okamsn via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-02-08 11:39 ` bug#68863: " Eli Zaretskii
  0 siblings, 2 replies; 13+ messages in thread
From: Okamsn via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-02-01  3:31 UTC (permalink / raw)
  To: 68863

[-- Attachment #1: Type: text/plain, Size: 881 bytes --]

Hello,

This patch adds support for using `seq-subseq` with `setf`, as in

     ;; => [0 1 2 10 11]
     (let ((seq (vector 0 1 2 3 4)))
       (setf (seq-subseq seq -2) (list 10 11 12 13 14))
       seq)

The patch adds a generic version which uses the existing `setf` support 
of `seq-elt` and a specialized version for modifying lists.  Both 
versions use `seq-do` to map a function over the values that should 
replace the values in the modified sequence.

To avoid modifying more values than specified, that modifying function 
uses a `when` condition. I'm not sure of a good way to stop `seq-do` 
early when we know that it can stop calling the modifying function. 
Normally, I would use `cl-block` and `cl-return`. Is it OK to use those 
features in `seq.el`? If not, is it worth adding something like a 
`seq-map-while` or a `seq-do-while`?

Thank you.

[-- Attachment #2: 0001-Add-setf-support-for-seq-subseq.patch --]
[-- Type: text/x-patch, Size: 5338 bytes --]

From 0a5fac443cdcbeb9312d7ee68bafdd22e0905828 Mon Sep 17 00:00:00 2001
From: Earl Hyatt <okamsn@protonmail.com>
Date: Sun, 28 Jan 2024 22:48:13 -0500
Subject: [PATCH] Add setf support for seq-subseq

* lisp/emacs-lisp/seq.el (seq-subseq): Add a generic version of
calling setf on seq-subseq and add a specialized version for when the
modified sequence is a list.
* test/lisp/emacs-lisp/seq-tests.el (test-setf-seq-subseq)
(test-setf-seq-subseq-combinations): Add tests for the feature.
---
 lisp/emacs-lisp/seq.el            | 43 +++++++++++++++++
 test/lisp/emacs-lisp/seq-tests.el | 76 +++++++++++++++++++++++++++++++
 2 files changed, 119 insertions(+)

diff --git a/lisp/emacs-lisp/seq.el b/lisp/emacs-lisp/seq.el
index 4c6553972c2..fd971806d87 100644
--- a/lisp/emacs-lisp/seq.el
+++ b/lisp/emacs-lisp/seq.el
@@ -193,6 +193,49 @@ seq-subseq
         (copy-sequence sequence))))
    (t (error "Unsupported sequence: %s" sequence))))
 
+(cl-defgeneric (setf seq-subseq) (store sequence start &optional end)
+  "Modify the elements of SEQUENCE from START to END to be those of STORE.
+
+SEQUENCE is neither lengthened nor shortened."
+  (let* ((len (seq-length sequence))
+         (idx (if (< start 0)
+                  (+ start len)
+                start))
+         (end (cond
+               ((null end) len)
+               ((< end 0)
+                (+ end len))
+               (t (min len end)))))
+    (when (< idx end)
+      (seq-do (lambda (v)
+                (when (< idx end)
+                  (setf (seq-elt sequence idx) v
+                        idx (1+ idx))))
+              store)))
+  store)
+
+(cl-defmethod (setf seq-subseq) (store (sequence list) start &optional end)
+  "Modify the elements of SEQUENCE from START to END to be those of STORE.
+
+SEQUENCE is neither lengthened nor shortened."
+  (let* ((len (seq-length sequence))
+         (idx (if (< start 0)
+                  (+ start len)
+                start))
+         (end (cond
+               ((null end) len)
+               ((< end 0)  (+ end len))
+               (t          (min len end)))))
+    (when (< idx end)
+      (seq-do (let ((replaced (nthcdr idx sequence)))
+                (lambda (v)
+                  (when (< idx end)
+                    (setf (car replaced) v
+                          replaced (cdr replaced)
+                          idx (1+ idx)))))
+              store)))
+  store)
+
 \f
 (cl-defgeneric seq-map (function sequence)
   "Return the result of applying FUNCTION to each element of SEQUENCE."
diff --git a/test/lisp/emacs-lisp/seq-tests.el b/test/lisp/emacs-lisp/seq-tests.el
index c06ceb00bdb..6b8789688d3 100644
--- a/test/lisp/emacs-lisp/seq-tests.el
+++ b/test/lisp/emacs-lisp/seq-tests.el
@@ -312,6 +312,82 @@ test-seq-subseq
                   (:success
                    (should (equal (seq-subseq list start end) res))))))))))))
 
+(cl-defmacro test-setf-seq-subseq-combinations (&key result range init-vals
+                                                     sub-vals)
+  "Produce substitutions tests for `seq-subseq' using `setf'.
+
+- INIT-VALS is a list holding the initial elements.
+- RESULT is what the final value should be after substitution.
+- SUB-VALS is a list holding the elements to be substituted in.
+- RANGE is a list of the `start' and `end' arguments of `seq-subseq'."
+  (let ((tests))
+    (dolist (type1 '(list vector string))
+      (dolist (type2 '(list vector string))
+        (push  `(should (equal (,type1 ,@result)
+                               (let ((seq (,type1 ,@init-vals)))
+                                 (setf (seq-subseq seq ,@range)
+                                       (,type2 ,@sub-vals))
+                                 seq)))
+               tests)))
+    `(progn ,@tests)))
+
+(ert-deftest test-setf-seq-subseq ()
+  "Test using `seq-subseq' with `setf'.
+Any combination of sequences should work."
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2)
+   :sub-vals (10 11 12)
+   :range (0)
+   :result (10 11 12))
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1)
+   :sub-vals (10 11 12)
+   :range (0)
+   :result (10 11))
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1)
+   :sub-vals (10 11 12)
+   :range (0 100)
+   :result (10 11))
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2 3 4)
+   :sub-vals (12 13 14 15)
+   :range (2 100)
+   :result (0 1 12 13 14))
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2 3 4)
+   :sub-vals (12 13 14 15)
+   :range (2 3)
+   :result (0 1 12 3 4))
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2 3 4)
+   :sub-vals (12 13 14 15)
+   :range (2 2)
+   :result (0 1 2 3 4))
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2 3 4 5 6 7 8 9)
+   :sub-vals (10 11 12 13 14)
+   :range (-2)
+   :result (0 1 2 10 11))
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2 3 4 5 6 7 8 9)
+   :sub-vals (10 11 12 13 14)
+   :range (-6 -3)
+   :result (0 1 2 3 10 11 12 7 8 9))
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2 3 4 5 6 7 8 9)
+   :sub-vals (10 11 12 13 14)
+   :range (-6 -10)
+   :result (0 1 2 3 4 5 6 7 8 9)))
+
 (ert-deftest test-seq-concatenate ()
   (with-test-sequences (seq '(2 4 6))
     (should (equal (seq-concatenate 'string seq [8]) (string 2 4 6 8)))
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* bug#68863: [PATCH] Add support for using setf with seq-subseq
  2024-02-01  3:31 bug#68863: Add support for using setf with seq-subseq Okamsn via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2024-02-04 18:33 ` Okamsn via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-02-08 11:39 ` bug#68863: " Eli Zaretskii
  1 sibling, 0 replies; 13+ messages in thread
From: Okamsn via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-02-04 18:33 UTC (permalink / raw)
  To: 68863; +Cc: Nicolas Petton

[-- Attachment #1: Type: text/plain, Size: 415 bytes --]

Hello,

I've added the maintainers e-mail address to the discussion and have 
added "[PATCH]" to the subject to make it more clear that there is a 
patch file attached.

I tested a version that uses a `seq-map-while`, but it only made a 
difference when the sequence of values to copy to the existing sequence 
was longer than 100 elements, which I would guess is a less common use case.

Thank you,
Earl

[-- Attachment #2: 0001-Add-setf-support-for-seq-subseq.patch --]
[-- Type: text/x-patch, Size: 5338 bytes --]

From 0a5fac443cdcbeb9312d7ee68bafdd22e0905828 Mon Sep 17 00:00:00 2001
From: Earl Hyatt <okamsn@protonmail.com>
Date: Sun, 28 Jan 2024 22:48:13 -0500
Subject: [PATCH] Add setf support for seq-subseq

* lisp/emacs-lisp/seq.el (seq-subseq): Add a generic version of
calling setf on seq-subseq and add a specialized version for when the
modified sequence is a list.
* test/lisp/emacs-lisp/seq-tests.el (test-setf-seq-subseq)
(test-setf-seq-subseq-combinations): Add tests for the feature.
---
 lisp/emacs-lisp/seq.el            | 43 +++++++++++++++++
 test/lisp/emacs-lisp/seq-tests.el | 76 +++++++++++++++++++++++++++++++
 2 files changed, 119 insertions(+)

diff --git a/lisp/emacs-lisp/seq.el b/lisp/emacs-lisp/seq.el
index 4c6553972c2..fd971806d87 100644
--- a/lisp/emacs-lisp/seq.el
+++ b/lisp/emacs-lisp/seq.el
@@ -193,6 +193,49 @@ seq-subseq
         (copy-sequence sequence))))
    (t (error "Unsupported sequence: %s" sequence))))
 
+(cl-defgeneric (setf seq-subseq) (store sequence start &optional end)
+  "Modify the elements of SEQUENCE from START to END to be those of STORE.
+
+SEQUENCE is neither lengthened nor shortened."
+  (let* ((len (seq-length sequence))
+         (idx (if (< start 0)
+                  (+ start len)
+                start))
+         (end (cond
+               ((null end) len)
+               ((< end 0)
+                (+ end len))
+               (t (min len end)))))
+    (when (< idx end)
+      (seq-do (lambda (v)
+                (when (< idx end)
+                  (setf (seq-elt sequence idx) v
+                        idx (1+ idx))))
+              store)))
+  store)
+
+(cl-defmethod (setf seq-subseq) (store (sequence list) start &optional end)
+  "Modify the elements of SEQUENCE from START to END to be those of STORE.
+
+SEQUENCE is neither lengthened nor shortened."
+  (let* ((len (seq-length sequence))
+         (idx (if (< start 0)
+                  (+ start len)
+                start))
+         (end (cond
+               ((null end) len)
+               ((< end 0)  (+ end len))
+               (t          (min len end)))))
+    (when (< idx end)
+      (seq-do (let ((replaced (nthcdr idx sequence)))
+                (lambda (v)
+                  (when (< idx end)
+                    (setf (car replaced) v
+                          replaced (cdr replaced)
+                          idx (1+ idx)))))
+              store)))
+  store)
+
 \f
 (cl-defgeneric seq-map (function sequence)
   "Return the result of applying FUNCTION to each element of SEQUENCE."
diff --git a/test/lisp/emacs-lisp/seq-tests.el b/test/lisp/emacs-lisp/seq-tests.el
index c06ceb00bdb..6b8789688d3 100644
--- a/test/lisp/emacs-lisp/seq-tests.el
+++ b/test/lisp/emacs-lisp/seq-tests.el
@@ -312,6 +312,82 @@ test-seq-subseq
                   (:success
                    (should (equal (seq-subseq list start end) res))))))))))))
 
+(cl-defmacro test-setf-seq-subseq-combinations (&key result range init-vals
+                                                     sub-vals)
+  "Produce substitutions tests for `seq-subseq' using `setf'.
+
+- INIT-VALS is a list holding the initial elements.
+- RESULT is what the final value should be after substitution.
+- SUB-VALS is a list holding the elements to be substituted in.
+- RANGE is a list of the `start' and `end' arguments of `seq-subseq'."
+  (let ((tests))
+    (dolist (type1 '(list vector string))
+      (dolist (type2 '(list vector string))
+        (push  `(should (equal (,type1 ,@result)
+                               (let ((seq (,type1 ,@init-vals)))
+                                 (setf (seq-subseq seq ,@range)
+                                       (,type2 ,@sub-vals))
+                                 seq)))
+               tests)))
+    `(progn ,@tests)))
+
+(ert-deftest test-setf-seq-subseq ()
+  "Test using `seq-subseq' with `setf'.
+Any combination of sequences should work."
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2)
+   :sub-vals (10 11 12)
+   :range (0)
+   :result (10 11 12))
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1)
+   :sub-vals (10 11 12)
+   :range (0)
+   :result (10 11))
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1)
+   :sub-vals (10 11 12)
+   :range (0 100)
+   :result (10 11))
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2 3 4)
+   :sub-vals (12 13 14 15)
+   :range (2 100)
+   :result (0 1 12 13 14))
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2 3 4)
+   :sub-vals (12 13 14 15)
+   :range (2 3)
+   :result (0 1 12 3 4))
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2 3 4)
+   :sub-vals (12 13 14 15)
+   :range (2 2)
+   :result (0 1 2 3 4))
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2 3 4 5 6 7 8 9)
+   :sub-vals (10 11 12 13 14)
+   :range (-2)
+   :result (0 1 2 10 11))
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2 3 4 5 6 7 8 9)
+   :sub-vals (10 11 12 13 14)
+   :range (-6 -3)
+   :result (0 1 2 3 10 11 12 7 8 9))
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2 3 4 5 6 7 8 9)
+   :sub-vals (10 11 12 13 14)
+   :range (-6 -10)
+   :result (0 1 2 3 4 5 6 7 8 9)))
+
 (ert-deftest test-seq-concatenate ()
   (with-test-sequences (seq '(2 4 6))
     (should (equal (seq-concatenate 'string seq [8]) (string 2 4 6 8)))
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* bug#68863: Add support for using setf with seq-subseq
  2024-02-01  3:31 bug#68863: Add support for using setf with seq-subseq Okamsn via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-02-04 18:33 ` bug#68863: [PATCH] " Okamsn via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2024-02-08 11:39 ` Eli Zaretskii
  2024-02-08 14:25   ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
  1 sibling, 1 reply; 13+ messages in thread
From: Eli Zaretskii @ 2024-02-08 11:39 UTC (permalink / raw)
  To: Okamsn, Nicolas Petton, Stefan Monnier; +Cc: 68863

> Date: Thu, 01 Feb 2024 03:31:10 +0000
> From:  Okamsn via "Bug reports for GNU Emacs,
>  the Swiss army knife of text editors" <bug-gnu-emacs@gnu.org>
> 
> This patch adds support for using `seq-subseq` with `setf`, as in
> 
>      ;; => [0 1 2 10 11]
>      (let ((seq (vector 0 1 2 3 4)))
>        (setf (seq-subseq seq -2) (list 10 11 12 13 14))
>        seq)
> 
> The patch adds a generic version which uses the existing `setf` support 
> of `seq-elt` and a specialized version for modifying lists.  Both 
> versions use `seq-do` to map a function over the values that should 
> replace the values in the modified sequence.
> 
> To avoid modifying more values than specified, that modifying function 
> uses a `when` condition. I'm not sure of a good way to stop `seq-do` 
> early when we know that it can stop calling the modifying function. 
> Normally, I would use `cl-block` and `cl-return`. Is it OK to use those 
> features in `seq.el`? If not, is it worth adding something like a 
> `seq-map-while` or a `seq-do-while`?

Thanks.

Nicolas, Stefan: any comments?  seq.el is preloaded, so we should
consider whether this addition is important enough to have it in seq
or elsewhere, if we think it's useful.





^ permalink raw reply	[flat|nested] 13+ messages in thread

* bug#68863: Add support for using setf with seq-subseq
  2024-02-08 11:39 ` bug#68863: " Eli Zaretskii
@ 2024-02-08 14:25   ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-02-09  3:54     ` Okamsn via Bug reports for GNU Emacs, the Swiss army knife of text editors
  0 siblings, 1 reply; 13+ messages in thread
From: Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-02-08 14:25 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: Okamsn, 68863, Nicolas Petton

>> This patch adds support for using `seq-subseq` with `setf`, as in
>> 
>>      ;; => [0 1 2 10 11]
>>      (let ((seq (vector 0 1 2 3 4)))
>>        (setf (seq-subseq seq -2) (list 10 11 12 13 14))
>>        seq)
>> 
>> The patch adds a generic version which uses the existing `setf` support 
>> of `seq-elt` and a specialized version for modifying lists.  Both 
>> versions use `seq-do` to map a function over the values that should 
>> replace the values in the modified sequence.
> Nicolas, Stefan: any comments?

Fine by me.

>> To avoid modifying more values than specified, that modifying function 
>> uses a `when` condition. I'm not sure of a good way to stop `seq-do` 
>> early when we know that it can stop calling the modifying function. 
>> Normally, I would use `cl-block` and `cl-return`. Is it OK to use those 
>> features in `seq.el`? If not, is it worth adding something like a 
>> `seq-map-while` or a `seq-do-while`?

`seq.el` is used by some parts of the implementation of `cl-lib`, so
the use of `cl-lib` risks introducing a circular dependency.  Maybe using
`cl-block/return` would be OK, but I wouldn't be surprised if it causes
bootstrap trouble.  You can use catch/throw, OTOH.


        Stefan






^ permalink raw reply	[flat|nested] 13+ messages in thread

* bug#68863: Add support for using setf with seq-subseq
  2024-02-08 14:25   ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2024-02-09  3:54     ` Okamsn via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-02-14  2:50       ` Okamsn via Bug reports for GNU Emacs, the Swiss army knife of text editors
  0 siblings, 1 reply; 13+ messages in thread
From: Okamsn via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-02-09  3:54 UTC (permalink / raw)
  To: Stefan Monnier, Eli Zaretskii; +Cc: 68863, Nicolas Petton

[-- Attachment #1: Type: text/plain, Size: 991 bytes --]

Stefan Monnier wrote:
>>> To avoid modifying more values than specified, that modifying function
>>> uses a `when` condition. I'm not sure of a good way to stop `seq-do`
>>> early when we know that it can stop calling the modifying function.
>>> Normally, I would use `cl-block` and `cl-return`. Is it OK to use those
>>> features in `seq.el`? If not, is it worth adding something like a
>>> `seq-map-while` or a `seq-do-while`?
> 
> `seq.el` is used by some parts of the implementation of `cl-lib`, so
> the use of `cl-lib` risks introducing a circular dependency.  Maybe using
> `cl-block/return` would be OK, but I wouldn't be surprised if it causes
> bootstrap trouble.  You can use catch/throw, OTOH.
> 
> 
>          Stefan
> 

Attached is an updated version using `catch` and `throw`. Thank you for 
pointing those out to me. The patch is also changed to signal 
`args-out-of-range` for the start and end indexes to be more like 
`seq-subseq`.

How does it look?

[-- Attachment #2: v2-0001-Add-setf-support-for-seq-subseq.patch --]
[-- Type: text/x-patch, Size: 10101 bytes --]

From b06db7905f5d6dfa0d33c05fd214ef95d19814b5 Mon Sep 17 00:00:00 2001
From: Earl Hyatt <okamsn@protonmail.com>
Date: Sun, 28 Jan 2024 22:48:13 -0500
Subject: [PATCH v2] Add setf support for seq-subseq.

* lisp/emacs-lisp/seq.el (seq-subseq): Add a generic version of
calling setf on seq-subseq and add a specialized version for when the
modified sequence is a list.
* test/lisp/emacs-lisp/seq-tests.el (test-setf-seq-subseq)
(test-setf-seq-subseq-combinations): Add tests for the feature.

The feature will signal 'args-out-of-range' if the starting index or
ending index (if given) is outside of the range of values from 0
through the length of the sequence or from the negative length of the
sequence through negative 1.  If the starting index is equal to the
length of the sequence, then nothing is changed.  If the starting
index is equal to the ending index, then nothing is changed.  The
feature should signal an error in all cases where using 'seq-subseq'
would signal an error.
---
 lisp/emacs-lisp/seq.el            |  88 ++++++++++++++++++
 test/lisp/emacs-lisp/seq-tests.el | 149 ++++++++++++++++++++++++++++++
 2 files changed, 237 insertions(+)

diff --git a/lisp/emacs-lisp/seq.el b/lisp/emacs-lisp/seq.el
index 4c6553972c2..6a1fd4c35e3 100644
--- a/lisp/emacs-lisp/seq.el
+++ b/lisp/emacs-lisp/seq.el
@@ -193,6 +193,94 @@ seq-subseq
         (copy-sequence sequence))))
    (t (error "Unsupported sequence: %s" sequence))))
 
+(cl-defgeneric (setf seq-subseq) (store sequence start &optional end)
+  "Modify the elements of SEQUENCE from START to END to be those of STORE.
+END is exclusive.
+
+If END is omitted, it defaults to the length of the sequence.  If
+START or END is negative, it counts from the end.  Signal an
+error if START or END are outside of the sequence (i.e too large
+if positive or too small if negative).
+
+SEQUENCE is neither lengthened nor shortened."
+  (let* ((len (seq-length sequence))
+         (signal-fn (lambda ()
+                      (signal 'args-out-of-range
+                              (if end
+                                  (list sequence start end)
+                                (list sequence start)))))
+         (signal-or-val-fn (lambda (val)
+                             (cond
+                              ((> val len)
+                               (funcall signal-fn))
+                              ((< val 0)
+                               (let ((val2 (+ val len)))
+                                 (if (< val2 0)
+                                     (funcall signal-fn)
+                                   val2)))
+                              (t
+                               val))))
+         (idx (funcall signal-or-val-fn start))
+         (idx-end (if (null end)
+                      len
+                    (funcall signal-or-val-fn end)))
+         (tag (gensym)))
+    (if (> idx idx-end)
+        (funcall signal-fn)
+      (catch tag
+        (seq-do (lambda (v)
+                  (if (< idx idx-end)
+                      (setf (seq-elt sequence idx) v
+                            idx (1+ idx))
+                    (throw tag nil)))
+                store))))
+  store)
+
+(cl-defmethod (setf seq-subseq) (store (sequence list) start &optional end)
+  "Modify the elements of SEQUENCE from START to END to be those of STORE.
+END is exclusive.
+
+If END is omitted, it defaults to the length of the sequence.  If
+START or END is negative, it counts from the end.  Signal an
+error if START or END are outside of the sequence (i.e too large
+if positive or too small if negative).
+
+SEQUENCE is neither lengthened nor shortened."
+  (let* ((len (seq-length sequence))
+         (signal-fn (lambda ()
+                      (signal 'args-out-of-range
+                              (if end
+                                  (list sequence start end)
+                                (list sequence start)))))
+         (signal-or-val-fn (lambda (val)
+                             (cond
+                              ((> val len)
+                               (funcall signal-fn))
+                              ((< val 0)
+                               (let ((val2 (+ val len)))
+                                 (if (< val2 0)
+                                     (funcall signal-fn)
+                                   val2)))
+                              (t
+                               val))))
+         (idx (funcall signal-or-val-fn start))
+         (idx-end (if (null end)
+                      len
+                    (funcall signal-or-val-fn end)))
+         (tag (gensym)))
+    (if (> idx idx-end)
+        (funcall signal-fn)
+      (catch tag
+        (seq-do (let ((replaced (nthcdr idx sequence)))
+                  (lambda (v)
+                    (if (< idx idx-end)
+                        (setf (car replaced) v
+                              replaced (cdr replaced)
+                              idx (1+ idx))
+                      (throw tag nil))))
+                store))))
+  store)
+
 \f
 (cl-defgeneric seq-map (function sequence)
   "Return the result of applying FUNCTION to each element of SEQUENCE."
diff --git a/test/lisp/emacs-lisp/seq-tests.el b/test/lisp/emacs-lisp/seq-tests.el
index c06ceb00bdb..d3e46c32f99 100644
--- a/test/lisp/emacs-lisp/seq-tests.el
+++ b/test/lisp/emacs-lisp/seq-tests.el
@@ -312,6 +312,155 @@ test-seq-subseq
                   (:success
                    (should (equal (seq-subseq list start end) res))))))))))))
 
+(cl-defmacro test-setf-seq-subseq-combinations
+    (&key init-vals sub-vals result range error)
+  "Make a test for each combination of sequence type for `seq-subseq' using `setf'.
+
+- INIT-VALS is a list holding the initial elements.
+- RESULT is what the final value should be after substitution.
+- ERROR is whether the form should signal `args-out-of-range'.
+- SUB-VALS is a list holding the elements to be substituted in.
+- RANGE is a list of the `start' and `end' arguments of `seq-subseq'."
+  (let ((tests))
+    (dolist (type1 '(list vector string))
+      (dolist (type2 '(list vector string))
+        (push  (if error
+                   `(should-error (let ((seq (,type1 ,@init-vals)))
+                                    (setf (seq-subseq seq ,@range)
+                                          (,type2 ,@sub-vals))
+                                    seq)
+                                  :type (quote args-out-of-range))
+                 `(should (equal (,type1 ,@result)
+                                 (let ((seq (,type1 ,@init-vals)))
+                                   (setf (seq-subseq seq ,@range)
+                                         (,type2 ,@sub-vals))
+                                   seq))))
+               tests)))
+    `(progn ,@tests)))
+
+(ert-deftest test-setf-seq-subseq ()
+  "Test using `seq-subseq' with `setf'.
+
+Any combination of sequences should work.
+
+An error should be signalled if the inclusive starting index or
+the exclusive ending index is out of the range from 0 through the
+length of the sequence, or if the starting index is greater than
+the ending index.  If the starting index is equal to the ending
+index, then nothing is changed.  If the starting index is equal
+to the length of the sequence, then nothing is changed.  It
+should signal an error in all the cases that `seq-subseq' signals
+an error."
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2)
+   :sub-vals (10 11 12)
+   :range (0)
+   :result (10 11 12))
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1)
+   :sub-vals (10 11 12)
+   :range (0)
+   :result (10 11))
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2)
+   :sub-vals (10 11 12)
+   :range (1)
+   :result (0 10 11))
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2)
+   :sub-vals (10 11 12)
+   :range (1 3)
+   :result (0 10 11))
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2)
+   :sub-vals (10 11 12)
+   :range (3 1)
+   :error t)
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2)
+   :sub-vals (10 11 12)
+   :range (1 100)
+   :error t)
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2)
+   :sub-vals (10 11 12)
+   :range (7)
+   :error t)
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2 3 4)
+   :sub-vals (12 13 14 15)
+   :range (2 3)
+   :result (0 1 12 3 4))
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2 3 4)
+   :sub-vals (12 13 14 15)
+   :range (2 2)
+   :result (0 1 2 3 4))
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2 3 4)
+   :sub-vals (12 13 14 15)
+   :range (5)
+   :result (0 1 2 3 4))
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2 3 4)
+   :sub-vals (12 13 14 15)
+   :range (6)
+   :error t)
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2 3 4)
+   :sub-vals (12 13 14 15)
+   :range (5 6)
+   :error t)
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2 3 4 5 6 7 8 9)
+   :sub-vals (10 11 12 13 14)
+   :range (-2)
+   :result (0 1 2 3 4 5 6 7 10 11))
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2 3 4 5 6 7 8 9)
+   :sub-vals (10 11 12 13 14)
+   :range (-6 -3)
+   :result (0 1 2 3 10 11 12 7 8 9))
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2 3 4 5 6 7 8 9)
+   :sub-vals (10 11 12 13 14)
+   :range (-6 -10)
+   :error t)
+
+  ;; This range might make sense, but since it would signal an error
+  ;; in `seq-subseq', we also signal an error in the `setf' feature.
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2 3 4 5 6 7 8 9)
+   :sub-vals (10 11 12 13 14)
+   :range (-6 0)
+   :error t)
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2 3 4 5 6 7 8 9)
+   :sub-vals (10 11 12 13 14)
+   :range (100)
+   :error t)
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2 3 4 5 6 7 8 9)
+   :sub-vals (10 11 12 13 14)
+   :range (-100)
+   :error t))
+
 (ert-deftest test-seq-concatenate ()
   (with-test-sequences (seq '(2 4 6))
     (should (equal (seq-concatenate 'string seq [8]) (string 2 4 6 8)))
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* bug#68863: Add support for using setf with seq-subseq
  2024-02-09  3:54     ` Okamsn via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2024-02-14  2:50       ` Okamsn via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-04-18  2:54         ` Okamsn via Bug reports for GNU Emacs, the Swiss army knife of text editors
  0 siblings, 1 reply; 13+ messages in thread
From: Okamsn via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-02-14  2:50 UTC (permalink / raw)
  To: Stefan Monnier, Eli Zaretskii; +Cc: 68863, Nicolas Petton

Okamsn wrote:
> Stefan Monnier wrote:
>>>> To avoid modifying more values than specified, that modifying function
>>>> uses a `when` condition. I'm not sure of a good way to stop `seq-do`
>>>> early when we know that it can stop calling the modifying function.
>>>> Normally, I would use `cl-block` and `cl-return`. Is it OK to use those
>>>> features in `seq.el`? If not, is it worth adding something like a
>>>> `seq-map-while` or a `seq-do-while`?
>>
>> `seq.el` is used by some parts of the implementation of `cl-lib`, so
>> the use of `cl-lib` risks introducing a circular dependency.  Maybe using
>> `cl-block/return` would be OK, but I wouldn't be surprised if it causes
>> bootstrap trouble.  You can use catch/throw, OTOH.
>>
>>
>>           Stefan
>>
> 
> Attached is an updated version using `catch` and `throw`. Thank you for
> pointing those out to me. The patch is also changed to signal
> `args-out-of-range` for the start and end indexes to be more like
> `seq-subseq`.
> 
> How does it look?

Hello,

After testing it more, I see that what I've written does not work as I 
expected in the case

(let ((v    (vector (vector 0 1)
                     (vector 2 3)
                     (vector 4 5))))
   (setf (seq-subseq (seq-subseq (elt v 0) 0) 0)
         [10])
   v)

in which I would expect it to replace the first element of the first 
sub-vector with 10. I will take more time to continue working on this.

Thank you for your patience.








^ permalink raw reply	[flat|nested] 13+ messages in thread

* bug#68863: Add support for using setf with seq-subseq
  2024-02-14  2:50       ` Okamsn via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2024-04-18  2:54         ` Okamsn via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-05-07  1:45           ` Okamsn via Bug reports for GNU Emacs, the Swiss army knife of text editors
  0 siblings, 1 reply; 13+ messages in thread
From: Okamsn via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-04-18  2:54 UTC (permalink / raw)
  To: Stefan Monnier, Eli Zaretskii; +Cc: 68863, Nicolas Petton

[-- Attachment #1: Type: text/plain, Size: 977 bytes --]

Okamsn wrote:
> Hello,
> 
> After testing it more, I see that what I've written does not work as I
> expected in the case
> 
> (let ((v    (vector (vector 0 1)
>                       (vector 2 3)
>                       (vector 4 5))))
>     (setf (seq-subseq (seq-subseq (elt v 0) 0) 0)
>           [10])
>     v)
> 
> in which I would expect it to replace the first element of the first
> sub-vector with 10. I will take more time to continue working on this.
> 
> Thank you for your patience.
> 
> 

Hello,

I found a way to work with subplaces, like in the example in my previous 
e-mail message. Instead of creating the generic feature `(setf 
seq-subseq)` like what is done for `seq-elt`, I created a generic 
function `seq-replace`, which is used in a new `gv-expander` for 
`seq-subseq`. This way of doing it is like what is done for `substring`, 
which has the behavior that I wanted.

What do you think about this approach?

Thank you.

[-- Attachment #2: v3-0001-Add-seq-replace-and-setf-support-for-seq-subseq.patch --]
[-- Type: text/x-patch, Size: 16721 bytes --]

From 414c7689ef8735e4d2955e0f97b5ce842120883e Mon Sep 17 00:00:00 2001
From: Earl Hyatt <okamsn@protonmail.com>
Date: Sun, 28 Jan 2024 22:48:13 -0500
Subject: [PATCH v3] Add seq-replace and setf support for seq-subseq.

* lisp/emacs-lisp/seq.el (seq-replace): Add function for
non-destructively replacing the elements of sequence
with those from another sequence.
* lisp/emacs-lisp/seq.el (seq-subseq): Declare the 'gv-expander'
specification using the new 'seq-replace' functions.
* test/lisp/emacs-lisp/seq-tests.el (test-seq-replace)
(test-seq-replace-combinations): Add tests for 'seq-replace'.
* test/lisp/emacs-lisp/seq-tests.el (test-setf-seq-subseq)
(test-setf-seq-subseq-combinations, test-setf-seq-subseq-recursive): Add
tests for the new gv expander.

The feature will signal 'args-out-of-range' if the starting index or
ending index (if given) is outside of the range of values from 0 through
the length of the sequence or from the negative length of the sequence
through negative 1.  If the starting index is equal to the length of the
sequence, then nothing is changed.  If the starting index is equal to
the ending index, then nothing is changed.  The 'seq-replace' and the
new 'setf' support for 'seq-subseq' should signal an error in all cases
where using 'seq-subseq' would signal an error.
---
 lisp/emacs-lisp/seq.el            |  91 +++++++++
 test/lisp/emacs-lisp/seq-tests.el | 324 ++++++++++++++++++++++++++++++
 2 files changed, 415 insertions(+)

diff --git a/lisp/emacs-lisp/seq.el b/lisp/emacs-lisp/seq.el
index a20cff16982..37f73932cd7 100644
--- a/lisp/emacs-lisp/seq.el
+++ b/lisp/emacs-lisp/seq.el
@@ -158,6 +158,84 @@ seq-copy
   "Return a shallow copy of SEQUENCE."
   (copy-sequence sequence))
 
+(cl-defgeneric seq-replace (sequence replacements start &optional end)
+  "Replace elements of SEQUENCE from START to END with elements of REPLACEMENTS.
+END is exclusive."
+  (let* ((len (seq-length sequence))
+         (signal-fn (lambda ()
+                      (signal 'args-out-of-range
+                              (if end
+                                  (list sequence start end)
+                                (list sequence start)))))
+         (signal-or-val-fn (lambda (val)
+                             (cond
+                              ((> val len)
+                               (funcall signal-fn))
+                              ((< val 0)
+                               (let ((val2 (+ val len)))
+                                 (if (< val2 0)
+                                     (funcall signal-fn)
+                                   val2)))
+                              (t
+                               val))))
+         (idx-start (funcall signal-or-val-fn start))
+         (idx-end (if (null end)
+                      len
+                    (funcall signal-or-val-fn end))))
+    (if (> idx-start idx-end)
+        (funcall signal-fn)
+      (let ((replacement-idx 0)
+            (replacement-len (seq-length replacements)))
+        (seq-into (seq-map-indexed (lambda (elem idx)
+                                     (if (and (<= idx-start idx)
+                                              (< idx idx-end)
+                                              (< replacement-idx replacement-len))
+                                         (prog1
+                                             (seq-elt replacements replacement-idx)
+                                           (setq replacement-idx (1+ replacement-idx)))
+                                       elem))
+                                   sequence)
+                  (if (listp sequence)
+                      'list
+                    (type-of sequence)))))))
+
+(cl-defmethod seq-replace (sequence (replacements list) start &optional end)
+  "Replace elements of SEQUENCE from START to END with elements of REPLACEMENTS.
+END is exclusive."
+  (let* ((len (seq-length sequence))
+         (signal-fn (lambda ()
+                      (signal 'args-out-of-range
+                              (if end
+                                  (list sequence start end)
+                                (list sequence start)))))
+         (signal-or-val-fn (lambda (val)
+                             (cond
+                              ((> val len)
+                               (funcall signal-fn))
+                              ((< val 0)
+                               (let ((val2 (+ val len)))
+                                 (if (< val2 0)
+                                     (funcall signal-fn)
+                                   val2)))
+                              (t
+                               val))))
+         (idx-start (funcall signal-or-val-fn start))
+         (idx-end (if (null end)
+                      len
+                    (funcall signal-or-val-fn end))))
+    (if (> idx-start idx-end)
+        (funcall signal-fn)
+      (seq-into (seq-map-indexed (lambda (elem idx)
+                                   (if (and (<= idx-start idx)
+                                            (< idx idx-end)
+                                            replacements)
+                                       (pop replacements)
+                                     elem))
+                                 sequence)
+                (if (listp sequence)
+                    'list
+                  (type-of sequence))))))
+
 ;;;###autoload
 (cl-defgeneric seq-subseq (sequence start &optional end)
   "Return the sequence of elements of SEQUENCE from START to END.
@@ -167,6 +245,19 @@ seq-subseq
 START or END is negative, it counts from the end.  Signal an
 error if START or END are outside of the sequence (i.e too large
 if positive or too small if negative)."
+  (declare
+   (gv-expander
+    (lambda (do)
+      (gv-letplace (getter setter) `(gv-delay-error ,sequence)
+        (macroexp-let2* nil ((start start) (end end))
+          (funcall do
+                   `(seq-subseq ,getter ,start ,end)
+                   (lambda (v)
+                     (macroexp-let2 nil v v
+                       `(progn
+                          ,(funcall setter
+                                    `(seq-replace ,getter ,v ,start ,end))
+                          ,v)))))))))
   (cond
    ((or (stringp sequence) (vectorp sequence)) (substring sequence start end))
    ((listp sequence)
diff --git a/test/lisp/emacs-lisp/seq-tests.el b/test/lisp/emacs-lisp/seq-tests.el
index c06ceb00bdb..44fd5350f72 100644
--- a/test/lisp/emacs-lisp/seq-tests.el
+++ b/test/lisp/emacs-lisp/seq-tests.el
@@ -312,6 +312,330 @@ test-seq-subseq
                   (:success
                    (should (equal (seq-subseq list start end) res))))))))))))
 
+(cl-defmacro test-seq-replace-combinations
+    (&key init-vals sub-vals result range error)
+  "Make a test for each combination of sequence type for `seq-subseq' using `setf'.
+
+- INIT-VALS is a list holding the initial elements.
+- RESULT is what the final value should be after substitution.
+- ERROR is whether the form should signal `args-out-of-range'.
+- SUB-VALS is a list holding the elements to be substituted in.
+- RANGE is a list of the `start' and `end' arguments of `seq-subseq'."
+  (let ((tests))
+    (dolist (type1 '(list vector string))
+      (dolist (type2 '(list vector string))
+        (push  (if error
+                   `(should-error (seq-replace (,type1 ,@init-vals)
+                                               (,type2 ,@sub-vals)
+                                               ,@range)
+                                  :type (quote args-out-of-range))
+                 `(should (equal (,type1 ,@result)
+                                 (seq-replace (,type1 ,@init-vals)
+                                              (,type2 ,@sub-vals)
+                                              ,@range))))
+               tests)))
+    `(progn ,@tests)))
+
+(ert-deftest test-seq-replace ()
+  "Test using `seq-replace' with `setf'.
+
+Any combination of sequences should work.
+
+An error should be signalled if the inclusive starting index or
+the exclusive ending index is out of the range from 0 through the
+length of the sequence, or if the starting index is greater than
+the ending index.  If the starting index is equal to the ending
+index, then nothing is changed.  If the starting index is equal
+to the length of the sequence, then nothing is changed.  It
+should signal an error in all the cases that `seq-replace' signals
+an error."
+  (test-seq-replace-combinations
+   :init-vals (0 1 2)
+   :sub-vals (10 11 12)
+   :range (0)
+   :result (10 11 12))
+
+  (test-seq-replace-combinations
+   :init-vals (0 1)
+   :sub-vals (10 11 12)
+   :range (0)
+   :result (10 11))
+
+  (test-seq-replace-combinations
+   :init-vals (0 1 2)
+   :sub-vals (10 11 12)
+   :range (1)
+   :result (0 10 11))
+
+  (test-seq-replace-combinations
+   :init-vals (0 1 2)
+   :sub-vals (10 11 12)
+   :range (1 3)
+   :result (0 10 11))
+
+  (test-seq-replace-combinations
+   :init-vals (0 1 2)
+   :sub-vals (10 11 12)
+   :range (3 1)
+   :error t)
+
+  (test-seq-replace-combinations
+   :init-vals (0 1 2)
+   :sub-vals (10 11 12)
+   :range (1 100)
+   :error t)
+
+  (test-seq-replace-combinations
+   :init-vals (0 1 2)
+   :sub-vals (10 11 12)
+   :range (7)
+   :error t)
+
+  (test-seq-replace-combinations
+   :init-vals (0 1 2 3 4)
+   :sub-vals (12 13 14 15)
+   :range (2 3)
+   :result (0 1 12 3 4))
+
+  (test-seq-replace-combinations
+   :init-vals (0 1 2 3 4)
+   :sub-vals (12 13 14 15)
+   :range (2 2)
+   :result (0 1 2 3 4))
+
+  (test-seq-replace-combinations
+   :init-vals (0 1 2 3 4)
+   :sub-vals (12 13 14 15)
+   :range (5)
+   :result (0 1 2 3 4))
+
+  (test-seq-replace-combinations
+   :init-vals (0 1 2 3 4)
+   :sub-vals (12 13 14 15)
+   :range (6)
+   :error t)
+
+  (test-seq-replace-combinations
+   :init-vals (0 1 2 3 4)
+   :sub-vals (12 13 14 15)
+   :range (5 6)
+   :error t)
+
+  (test-seq-replace-combinations
+   :init-vals (0 1 2 3 4 5 6 7 8 9)
+   :sub-vals (10 11 12 13 14)
+   :range (-2)
+   :result (0 1 2 3 4 5 6 7 10 11))
+
+  (test-seq-replace-combinations
+   :init-vals (0 1 2 3 4 5 6 7 8 9)
+   :sub-vals (10 11 12 13 14)
+   :range (-6 -3)
+   :result (0 1 2 3 10 11 12 7 8 9))
+
+  (test-seq-replace-combinations
+   :init-vals (0 1 2 3 4 5 6 7 8 9)
+   :sub-vals (10 11 12 13 14)
+   :range (-6 -10)
+   :error t)
+
+  ;; This range might make sense, but since it would signal an error
+  ;; in `seq-subseq', we also signal an error in the `setf' feature.
+  (test-seq-replace-combinations
+   :init-vals (0 1 2 3 4 5 6 7 8 9)
+   :sub-vals (10 11 12 13 14)
+   :range (-6 0)
+   :error t)
+
+  (test-seq-replace-combinations
+   :init-vals (0 1 2 3 4 5 6 7 8 9)
+   :sub-vals (10 11 12 13 14)
+   :range (100)
+   :error t)
+
+  (test-seq-replace-combinations
+   :init-vals (0 1 2 3 4 5 6 7 8 9)
+   :sub-vals (10 11 12 13 14)
+   :range (-100)
+   :error t))
+
+(cl-defmacro test-setf-seq-subseq-combinations
+    (&key init-vals sub-vals result range error)
+  "Make a test for each combination of sequence type for `seq-subseq' using `setf'.
+
+- INIT-VALS is a list holding the initial elements.
+- RESULT is what the final value should be after substitution.
+- ERROR is whether the form should signal `args-out-of-range'.
+- SUB-VALS is a list holding the elements to be substituted in.
+- RANGE is a list of the `start' and `end' arguments of `seq-subseq'."
+  (let ((tests))
+    (dolist (type1 '(list vector string))
+      (dolist (type2 '(list vector string))
+        (push  (if error
+                   `(should-error (let ((seq (,type1 ,@init-vals)))
+                                    (setf (seq-subseq seq ,@range)
+                                          (,type2 ,@sub-vals))
+                                    seq)
+                                  :type (quote args-out-of-range))
+                 `(should (equal (,type1 ,@result)
+                                 (let ((seq (,type1 ,@init-vals)))
+                                   (setf (seq-subseq seq ,@range)
+                                         (,type2 ,@sub-vals))
+                                   seq))))
+               tests)))
+    `(progn ,@tests)))
+
+(ert-deftest test-setf-seq-subseq ()
+  "Test using `seq-subseq' with `setf'.
+
+Any combination of sequences should work.
+
+An error should be signalled if the inclusive starting index or
+the exclusive ending index is out of the range from 0 through the
+length of the sequence, or if the starting index is greater than
+the ending index.  If the starting index is equal to the ending
+index, then nothing is changed.  If the starting index is equal
+to the length of the sequence, then nothing is changed.  It
+should signal an error in all the cases that `seq-subseq' signals
+an error."
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2)
+   :sub-vals (10 11 12)
+   :range (0)
+   :result (10 11 12))
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1)
+   :sub-vals (10 11 12)
+   :range (0)
+   :result (10 11))
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2)
+   :sub-vals (10 11 12)
+   :range (1)
+   :result (0 10 11))
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2)
+   :sub-vals (10 11 12)
+   :range (1 3)
+   :result (0 10 11))
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2)
+   :sub-vals (10 11 12)
+   :range (3 1)
+   :error t)
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2)
+   :sub-vals (10 11 12)
+   :range (1 100)
+   :error t)
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2)
+   :sub-vals (10 11 12)
+   :range (7)
+   :error t)
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2 3 4)
+   :sub-vals (12 13 14 15)
+   :range (2 3)
+   :result (0 1 12 3 4))
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2 3 4)
+   :sub-vals (12 13 14 15)
+   :range (2 2)
+   :result (0 1 2 3 4))
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2 3 4)
+   :sub-vals (12 13 14 15)
+   :range (5)
+   :result (0 1 2 3 4))
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2 3 4)
+   :sub-vals (12 13 14 15)
+   :range (6)
+   :error t)
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2 3 4)
+   :sub-vals (12 13 14 15)
+   :range (5 6)
+   :error t)
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2 3 4 5 6 7 8 9)
+   :sub-vals (10 11 12 13 14)
+   :range (-2)
+   :result (0 1 2 3 4 5 6 7 10 11))
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2 3 4 5 6 7 8 9)
+   :sub-vals (10 11 12 13 14)
+   :range (-6 -3)
+   :result (0 1 2 3 10 11 12 7 8 9))
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2 3 4 5 6 7 8 9)
+   :sub-vals (10 11 12 13 14)
+   :range (-6 -10)
+   :error t)
+
+  ;; This range might make sense, but since it would signal an error
+  ;; in `seq-subseq', we also signal an error in the `setf' feature.
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2 3 4 5 6 7 8 9)
+   :sub-vals (10 11 12 13 14)
+   :range (-6 0)
+   :error t)
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2 3 4 5 6 7 8 9)
+   :sub-vals (10 11 12 13 14)
+   :range (100)
+   :error t)
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2 3 4 5 6 7 8 9)
+   :sub-vals (10 11 12 13 14)
+   :range (-100)
+   :error t))
+
+(ert-deftest test-setf-seq-subseq-recursive ()
+  "Test using `setf' with `seq-subseq' on sub-places.
+Like using `setf' with `substring'."
+  (let ((vect (vector 0 1 2 3 4 5 6)))
+    (setf (seq-subseq (seq-subseq vect 2) 2) [111 222])
+    (should (equal vect (vector 0 1 2 3 111 222 6))))
+
+  (let ((str (string ?a ?b ?c ?d ?e ?f ?g)))
+    (setf (seq-subseq (seq-subseq str 2) 0) (list ?x ?y ?z ?1 ?2 ?3))
+    (should (equal str (string ?a ?b ?x ?y ?z ?1 ?2))))
+
+  (let ((lst (list ?a ?b ?c ?d ?e ?f ?g)))
+    (setf (seq-subseq (seq-subseq (seq-subseq (seq-subseq lst 0)
+                                              0)
+                                  2)
+                      -5
+                      -3)
+          (vector ?x ?y ?z ?1 ?2 ?3))
+    (should (equal lst (list ?a ?b ?x ?y ?e ?f ?g))))
+
+  (let ((lst (list 0 1 2 3 4 5 6 7 8 9)))
+    (setf (seq-subseq (seq-subseq (seq-subseq (seq-subseq lst 1)
+                                              1)
+                                  1)
+                      1)
+          (vector 111 222 333 444 555 666 777 888))
+    (should (equal lst (list 0 1 2 3 111 222 333 444 555 666)))))
+
 (ert-deftest test-seq-concatenate ()
   (with-test-sequences (seq '(2 4 6))
     (should (equal (seq-concatenate 'string seq [8]) (string 2 4 6 8)))
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* bug#68863: Add support for using setf with seq-subseq
  2024-04-18  2:54         ` Okamsn via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2024-05-07  1:45           ` Okamsn via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-05-08 21:01             ` Michael Heerdegen via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-05-09 12:16             ` Michael Heerdegen via Bug reports for GNU Emacs, the Swiss army knife of text editors
  0 siblings, 2 replies; 13+ messages in thread
From: Okamsn via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-05-07  1:45 UTC (permalink / raw)
  To: Stefan Monnier, Eli Zaretskii, Michael Heerdegen; +Cc: 68863, Nicolas Petton

[-- Attachment #1: Type: text/plain, Size: 1350 bytes --]

Okamsn wrote:
> Okamsn wrote:
>> Hello,
>>
>> After testing it more, I see that what I've written does not work as I
>> expected in the case
>>
>> (let ((v    (vector (vector 0 1)
>>                        (vector 2 3)
>>                        (vector 4 5))))
>>      (setf (seq-subseq (seq-subseq (elt v 0) 0) 0)
>>            [10])
>>      v)
>>
>> in which I would expect it to replace the first element of the first
>> sub-vector with 10. I will take more time to continue working on this.
>>
>> Thank you for your patience.
>>
>>
> 
> Hello,
> 
> I found a way to work with subplaces, like in the example in my previous
> e-mail message. Instead of creating the generic feature `(setf
> seq-subseq)` like what is done for `seq-elt`, I created a generic
> function `seq-replace`, which is used in a new `gv-expander` for
> `seq-subseq`. This way of doing it is like what is done for `substring`,
> which has the behavior that I wanted.
> 
> What do you think about this approach?
> 
> Thank you.

Hello,

Since supporting sub-places is controversial, would you please review 
version 2 of the patch that I sent, which I have re-attached for 
convenience. This version /does not/ support sub-places.

I have added Michael Heerdegen to the recipients list in case they would 
like to comment.

Thank you.

[-- Attachment #2: v2-0001-Add-setf-support-for-seq-subseq.patch --]
[-- Type: text/x-patch, Size: 10101 bytes --]

From b06db7905f5d6dfa0d33c05fd214ef95d19814b5 Mon Sep 17 00:00:00 2001
From: Earl Hyatt <okamsn@protonmail.com>
Date: Sun, 28 Jan 2024 22:48:13 -0500
Subject: [PATCH v2] Add setf support for seq-subseq.

* lisp/emacs-lisp/seq.el (seq-subseq): Add a generic version of
calling setf on seq-subseq and add a specialized version for when the
modified sequence is a list.
* test/lisp/emacs-lisp/seq-tests.el (test-setf-seq-subseq)
(test-setf-seq-subseq-combinations): Add tests for the feature.

The feature will signal 'args-out-of-range' if the starting index or
ending index (if given) is outside of the range of values from 0
through the length of the sequence or from the negative length of the
sequence through negative 1.  If the starting index is equal to the
length of the sequence, then nothing is changed.  If the starting
index is equal to the ending index, then nothing is changed.  The
feature should signal an error in all cases where using 'seq-subseq'
would signal an error.
---
 lisp/emacs-lisp/seq.el            |  88 ++++++++++++++++++
 test/lisp/emacs-lisp/seq-tests.el | 149 ++++++++++++++++++++++++++++++
 2 files changed, 237 insertions(+)

diff --git a/lisp/emacs-lisp/seq.el b/lisp/emacs-lisp/seq.el
index 4c6553972c2..6a1fd4c35e3 100644
--- a/lisp/emacs-lisp/seq.el
+++ b/lisp/emacs-lisp/seq.el
@@ -193,6 +193,94 @@ seq-subseq
         (copy-sequence sequence))))
    (t (error "Unsupported sequence: %s" sequence))))
 
+(cl-defgeneric (setf seq-subseq) (store sequence start &optional end)
+  "Modify the elements of SEQUENCE from START to END to be those of STORE.
+END is exclusive.
+
+If END is omitted, it defaults to the length of the sequence.  If
+START or END is negative, it counts from the end.  Signal an
+error if START or END are outside of the sequence (i.e too large
+if positive or too small if negative).
+
+SEQUENCE is neither lengthened nor shortened."
+  (let* ((len (seq-length sequence))
+         (signal-fn (lambda ()
+                      (signal 'args-out-of-range
+                              (if end
+                                  (list sequence start end)
+                                (list sequence start)))))
+         (signal-or-val-fn (lambda (val)
+                             (cond
+                              ((> val len)
+                               (funcall signal-fn))
+                              ((< val 0)
+                               (let ((val2 (+ val len)))
+                                 (if (< val2 0)
+                                     (funcall signal-fn)
+                                   val2)))
+                              (t
+                               val))))
+         (idx (funcall signal-or-val-fn start))
+         (idx-end (if (null end)
+                      len
+                    (funcall signal-or-val-fn end)))
+         (tag (gensym)))
+    (if (> idx idx-end)
+        (funcall signal-fn)
+      (catch tag
+        (seq-do (lambda (v)
+                  (if (< idx idx-end)
+                      (setf (seq-elt sequence idx) v
+                            idx (1+ idx))
+                    (throw tag nil)))
+                store))))
+  store)
+
+(cl-defmethod (setf seq-subseq) (store (sequence list) start &optional end)
+  "Modify the elements of SEQUENCE from START to END to be those of STORE.
+END is exclusive.
+
+If END is omitted, it defaults to the length of the sequence.  If
+START or END is negative, it counts from the end.  Signal an
+error if START or END are outside of the sequence (i.e too large
+if positive or too small if negative).
+
+SEQUENCE is neither lengthened nor shortened."
+  (let* ((len (seq-length sequence))
+         (signal-fn (lambda ()
+                      (signal 'args-out-of-range
+                              (if end
+                                  (list sequence start end)
+                                (list sequence start)))))
+         (signal-or-val-fn (lambda (val)
+                             (cond
+                              ((> val len)
+                               (funcall signal-fn))
+                              ((< val 0)
+                               (let ((val2 (+ val len)))
+                                 (if (< val2 0)
+                                     (funcall signal-fn)
+                                   val2)))
+                              (t
+                               val))))
+         (idx (funcall signal-or-val-fn start))
+         (idx-end (if (null end)
+                      len
+                    (funcall signal-or-val-fn end)))
+         (tag (gensym)))
+    (if (> idx idx-end)
+        (funcall signal-fn)
+      (catch tag
+        (seq-do (let ((replaced (nthcdr idx sequence)))
+                  (lambda (v)
+                    (if (< idx idx-end)
+                        (setf (car replaced) v
+                              replaced (cdr replaced)
+                              idx (1+ idx))
+                      (throw tag nil))))
+                store))))
+  store)
+
 \f
 (cl-defgeneric seq-map (function sequence)
   "Return the result of applying FUNCTION to each element of SEQUENCE."
diff --git a/test/lisp/emacs-lisp/seq-tests.el b/test/lisp/emacs-lisp/seq-tests.el
index c06ceb00bdb..d3e46c32f99 100644
--- a/test/lisp/emacs-lisp/seq-tests.el
+++ b/test/lisp/emacs-lisp/seq-tests.el
@@ -312,6 +312,155 @@ test-seq-subseq
                   (:success
                    (should (equal (seq-subseq list start end) res))))))))))))
 
+(cl-defmacro test-setf-seq-subseq-combinations
+    (&key init-vals sub-vals result range error)
+  "Make a test for each combination of sequence type for `seq-subseq' using `setf'.
+
+- INIT-VALS is a list holding the initial elements.
+- RESULT is what the final value should be after substitution.
+- ERROR is whether the form should signal `args-out-of-range'.
+- SUB-VALS is a list holding the elements to be substituted in.
+- RANGE is a list of the `start' and `end' arguments of `seq-subseq'."
+  (let ((tests))
+    (dolist (type1 '(list vector string))
+      (dolist (type2 '(list vector string))
+        (push  (if error
+                   `(should-error (let ((seq (,type1 ,@init-vals)))
+                                    (setf (seq-subseq seq ,@range)
+                                          (,type2 ,@sub-vals))
+                                    seq)
+                                  :type (quote args-out-of-range))
+                 `(should (equal (,type1 ,@result)
+                                 (let ((seq (,type1 ,@init-vals)))
+                                   (setf (seq-subseq seq ,@range)
+                                         (,type2 ,@sub-vals))
+                                   seq))))
+               tests)))
+    `(progn ,@tests)))
+
+(ert-deftest test-setf-seq-subseq ()
+  "Test using `seq-subseq' with `setf'.
+
+Any combination of sequences should work.
+
+An error should be signalled if the inclusive starting index or
+the exclusive ending index is out of the range from 0 through the
+length of the sequence, or if the starting index is greater than
+the ending index.  If the starting index is equal to the ending
+index, then nothing is changed.  If the starting index is equal
+to the length of the sequence, then nothing is changed.  It
+should signal an error in all the cases that `seq-subseq' signals
+an error."
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2)
+   :sub-vals (10 11 12)
+   :range (0)
+   :result (10 11 12))
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1)
+   :sub-vals (10 11 12)
+   :range (0)
+   :result (10 11))
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2)
+   :sub-vals (10 11 12)
+   :range (1)
+   :result (0 10 11))
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2)
+   :sub-vals (10 11 12)
+   :range (1 3)
+   :result (0 10 11))
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2)
+   :sub-vals (10 11 12)
+   :range (3 1)
+   :error t)
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2)
+   :sub-vals (10 11 12)
+   :range (1 100)
+   :error t)
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2)
+   :sub-vals (10 11 12)
+   :range (7)
+   :error t)
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2 3 4)
+   :sub-vals (12 13 14 15)
+   :range (2 3)
+   :result (0 1 12 3 4))
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2 3 4)
+   :sub-vals (12 13 14 15)
+   :range (2 2)
+   :result (0 1 2 3 4))
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2 3 4)
+   :sub-vals (12 13 14 15)
+   :range (5)
+   :result (0 1 2 3 4))
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2 3 4)
+   :sub-vals (12 13 14 15)
+   :range (6)
+   :error t)
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2 3 4)
+   :sub-vals (12 13 14 15)
+   :range (5 6)
+   :error t)
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2 3 4 5 6 7 8 9)
+   :sub-vals (10 11 12 13 14)
+   :range (-2)
+   :result (0 1 2 3 4 5 6 7 10 11))
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2 3 4 5 6 7 8 9)
+   :sub-vals (10 11 12 13 14)
+   :range (-6 -3)
+   :result (0 1 2 3 10 11 12 7 8 9))
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2 3 4 5 6 7 8 9)
+   :sub-vals (10 11 12 13 14)
+   :range (-6 -10)
+   :error t)
+
+  ;; This range might make sense, but since it would signal an error
+  ;; in `seq-subseq', we also signal an error in the `setf' feature.
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2 3 4 5 6 7 8 9)
+   :sub-vals (10 11 12 13 14)
+   :range (-6 0)
+   :error t)
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2 3 4 5 6 7 8 9)
+   :sub-vals (10 11 12 13 14)
+   :range (100)
+   :error t)
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2 3 4 5 6 7 8 9)
+   :sub-vals (10 11 12 13 14)
+   :range (-100)
+   :error t))
+
 (ert-deftest test-seq-concatenate ()
   (with-test-sequences (seq '(2 4 6))
     (should (equal (seq-concatenate 'string seq [8]) (string 2 4 6 8)))
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* bug#68863: Add support for using setf with seq-subseq
  2024-05-07  1:45           ` Okamsn via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2024-05-08 21:01             ` Michael Heerdegen via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-05-09 12:16             ` Michael Heerdegen via Bug reports for GNU Emacs, the Swiss army knife of text editors
  1 sibling, 0 replies; 13+ messages in thread
From: Michael Heerdegen via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-05-08 21:01 UTC (permalink / raw)
  To: Okamsn; +Cc: 68863, Eli Zaretskii, Nicolas Petton, Stefan Monnier

Okamsn <okamsn@protonmail.com> writes:

> Subject: [PATCH v2] Add setf support for seq-subseq.

Thank you for the update.

I wonder about the implementation for listp STOREs:

> +(cl-defmethod (setf seq-subseq) (store (sequence list) start &optional end)
> +  "Modify the elements of SEQUENCE from START to END to be those of STORE.
> +END is exclusive.
> +
> +If END is omitted, it defaults to the length of the sequence.  If
> +START or END is negative, it counts from the end. [...]

In the case where we don't count from the end:

> +error if START or END are outside of the sequence (i.e too large
> +if positive or too small if negative).
> +
> +SEQUENCE is neither lengthened nor shortened."
> +  (let* ((len (seq-length sequence))

running through the complete list just to get its length which we don't
need is a waste of time - right?

> +      (catch tag
> +        (seq-do (let ((replaced (nthcdr idx sequence)))
> +                  (lambda (v)
> +                    (if (< idx idx-end)
> +                        (setf (car replaced) v
> +                              replaced (cdr replaced)
> +                              idx (1+ idx))
> +                      (throw tag nil))))
> +                store))))
> +  store)

And wouldn't it here be more efficient to use `setcar' and `setcdr'
manipulations to insert the complete sequence (if it's a list, else we
may convert it into one) in one go instead of replacing elements one by
one?


Thanks,

Michael.





^ permalink raw reply	[flat|nested] 13+ messages in thread

* bug#68863: Add support for using setf with seq-subseq
  2024-05-07  1:45           ` Okamsn via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-05-08 21:01             ` Michael Heerdegen via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2024-05-09 12:16             ` Michael Heerdegen via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-05-09 13:55               ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-05-14 12:47               ` Okamsn via Bug reports for GNU Emacs, the Swiss army knife of text editors
  1 sibling, 2 replies; 13+ messages in thread
From: Michael Heerdegen via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-05-09 12:16 UTC (permalink / raw)
  To: 68863; +Cc: okamsn, eliz, nicolas, monnier

Hello again,

There is a second point I want to hint at/ discuss:

Some time ago we have removed lots of or even the majority of the
so far implemented generalized variables.

The main criteria were: are they useful, and is the semantics clear.  We
should think about whether this is the case here.

For `seq-subseq' the semantics is not completely unambiguous.  For
example, the setter could exchange a subsequence - but also simply
insert an additional piece, creating a longer sequence.  Both operations
can be useful.

Likewise, when an index (e.g. the starting position) is going beyond the
end of a sequence, Emacs could raise an error, or provide functionality
to append to the existing sequence.  Again, both kinds of behavior could
make sense in different situations.

Finally, the question of whether the operation is destructive, or which
of the involved sequences are reused, is not trivial.

Any thoughts about these points?  Is this generalized variable useful
enough to make this acceptable?  Which kind of implementation do we
prefer then?


Michael.





^ permalink raw reply	[flat|nested] 13+ messages in thread

* bug#68863: Add support for using setf with seq-subseq
  2024-05-09 12:16             ` Michael Heerdegen via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2024-05-09 13:55               ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-05-14 12:47               ` Okamsn via Bug reports for GNU Emacs, the Swiss army knife of text editors
  1 sibling, 0 replies; 13+ messages in thread
From: Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-05-09 13:55 UTC (permalink / raw)
  To: Michael Heerdegen; +Cc: 68863, okamsn, eliz, nicolas

> Some time ago we have removed lots of or even the majority of the
> so far implemented generalized variables.
>
> The main criteria were: are they useful, and is the semantics clear.  We
> should think about whether this is the case here.

Agreed.  We should not lose track of the fact that we're talking about
"generalized variables".  Treating `(seq-subset ...)` as a "generalized
variable" (aka a "place") is stretching the idea, which we can see in the
fact that there are several different alternative ways to implement the
feature with incompatible semantics.

IOW, as a general rule a function that returns a fresh new value is
often not a good candidate for a "gv-place".  That doesn't mean we can't
accept such a change (I'm pretty sure we do have and (ab)use such
gv-places), but that it has to be weighed against its practical
usefulness.

IOW, I'd like to see existing code where we could make use of it to
simplify the code.  That might also help decide decide which semantics
would be preferable.


        Stefan






^ permalink raw reply	[flat|nested] 13+ messages in thread

* bug#68863: Add support for using setf with seq-subseq
  2024-05-09 12:16             ` Michael Heerdegen via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-05-09 13:55               ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2024-05-14 12:47               ` Okamsn via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-05-14 15:52                 ` Michael Heerdegen via Bug reports for GNU Emacs, the Swiss army knife of text editors
  1 sibling, 1 reply; 13+ messages in thread
From: Okamsn via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-05-14 12:47 UTC (permalink / raw)
  To: michael_heerdegen, 68863; +Cc: eliz, nicolas, monnier

[-- Attachment #1: Type: text/plain, Size: 2514 bytes --]

Michael Heerdegen wrote:
> Hello again,
> 
> There is a second point I want to hint at/ discuss:
> 
> Some time ago we have removed lots of or even the majority of the
> so far implemented generalized variables.
> 
> The main criteria were: are they useful, and is the semantics clear.  We
> should think about whether this is the case here.
> 
> For `seq-subseq' the semantics is not completely unambiguous.  For
> example, the setter could exchange a subsequence - but also simply
> insert an additional piece, creating a longer sequence.  Both operations
> can be useful.
> 
> Likewise, when an index (e.g. the starting position) is going beyond the
> end of a sequence, Emacs could raise an error, or provide functionality
> to append to the existing sequence.  Again, both kinds of behavior could
> make sense in different situations.
> 
> Finally, the question of whether the operation is destructive, or which
> of the involved sequences are reused, is not trivial.
> 
> Any thoughts about these points?  Is this generalized variable useful
> enough to make this acceptable?  Which kind of implementation do we
> prefer then?
> 
> 
> Michael.

Hello,

Here is what I have thought:

1. The setter should be consistent for different kinds of sequences. My 
understanding is that arrays cannot be extended, so I think that it 
should not be able to extend lists either. I think that something like a 
non-destructive `seq-replace` or a non-destructive `seq-splice` is a 
separate feature.

2. The setter should be consistent with how `seq-subseq` raises errors. 
`seq-subseq` raises an error if a given index is greater than the 
sequence's length, so the setter should too.

3. The setter should not modify the sequence containing the replacement 
values. The manual states: "All functions defined in this library are 
free of side-effects; i.e., they do not modify any sequence (list, 
vector, or string) that you pass as an argument." I think that it makes 
sense for the setter to modify the target sequence, because `setf` is 
used to set places to values, but I think that it would be unexpected to 
modify the sequence of replacement values and that it would be contrary 
to what the manual states and how the other features behave.

I will try to find uses of `cl-replace` and maybe uses of `append` and 
`nconc` for examples. Also, I have attached a version of the patch that 
does not compute the length of the list unless needed.

Thank you.

[-- Attachment #2: v4-0001-Add-setf-support-for-seq-subseq.patch --]
[-- Type: text/x-patch, Size: 10655 bytes --]

From 89c6800e4e788d6a121114bc16f47e1bbc2ee814 Mon Sep 17 00:00:00 2001
From: Earl Hyatt <okamsn@protonmail.com>
Date: Sun, 28 Jan 2024 22:48:13 -0500
Subject: [PATCH v4] Add setf support for seq-subseq.

* lisp/emacs-lisp/seq.el (seq-subseq): Add a generic version of
calling setf on seq-subseq and add a specialized version for when the
modified sequence is a list.
* test/lisp/emacs-lisp/seq-tests.el (test-setf-seq-subseq)
(test-setf-seq-subseq-combinations): Add tests for the feature.

The feature will signal 'args-out-of-range' if the starting index or
ending index (if given) is outside of the range of values from 0
through the length of the sequence or from the negative length of the
sequence through negative 1.  If the starting index is equal to the
length of the sequence, then nothing is changed.  If the starting
index is equal to the ending index, then nothing is changed.  The
feature should signal an error in all cases where using 'seq-subseq'
would signal an error.
---
 lisp/emacs-lisp/seq.el            | 104 +++++++++++++++++++++
 test/lisp/emacs-lisp/seq-tests.el | 149 ++++++++++++++++++++++++++++++
 2 files changed, 253 insertions(+)

diff --git a/lisp/emacs-lisp/seq.el b/lisp/emacs-lisp/seq.el
index a20cff16982..95688a60645 100644
--- a/lisp/emacs-lisp/seq.el
+++ b/lisp/emacs-lisp/seq.el
@@ -193,6 +193,110 @@ seq-subseq
         (copy-sequence sequence))))
    (t (error "Unsupported sequence: %s" sequence))))
 
+(cl-defgeneric (setf seq-subseq) (store sequence start &optional end)
+  "Modify the elements of SEQUENCE from START to END to be those of STORE.
+END is exclusive.
+
+If END is omitted, it defaults to the length of the sequence.  If
+START or END is negative, it counts from the end.  Signal an
+error if START or END are outside of the sequence (i.e too large
+if positive or too small if negative).
+
+SEQUENCE is neither lengthened nor shortened."
+  (let* ((len (seq-length sequence))
+         (signal-fn (lambda ()
+                      (signal 'args-out-of-range
+                              (if end
+                                  (list sequence start end)
+                                (list sequence start)))))
+         (signal-or-val-fn (lambda (val)
+                             (cond
+                              ((> val len)
+                               (funcall signal-fn))
+                              ((< val 0)
+                               (let ((val2 (+ val len)))
+                                 (if (< val2 0)
+                                     (funcall signal-fn)
+                                   val2)))
+                              (t
+                               val))))
+         (idx (funcall signal-or-val-fn start))
+         (idx-end (if (null end)
+                      len
+                    (funcall signal-or-val-fn end)))
+         (tag (gensym)))
+    (if (> idx idx-end)
+        (funcall signal-fn)
+      (catch tag
+        (seq-do (lambda (v)
+                  (if (< idx idx-end)
+                      (setf (seq-elt sequence idx) v
+                            idx (1+ idx))
+                    (throw tag nil)))
+                store))))
+  store)
+
+(cl-defmethod (setf seq-subseq) (store (sequence list) start &optional end)
+  "Modify the elements of SEQUENCE from START to END to be those of STORE.
+END is exclusive.
+
+If END is omitted, it defaults to the length of the sequence.  If
+START or END is negative, it counts from the end.  Signal an
+error if START or END are outside of the sequence (i.e too large
+if positive or too small if negative).
+
+SEQUENCE is neither lengthened nor shortened."
+  (let* ((arg-start start)
+         (arg-end end)
+         (signal-fn (lambda ()
+                      (signal 'args-out-of-range
+                              (if arg-end
+                                  (list sequence arg-start arg-end)
+                                (list sequence arg-start)))))
+         (len)
+         (neg-start (< start 0))
+         (neg-end (and end (< end 0))))
+    ;; Avoid calculating the length of the list if we don't need too.
+    (when (or neg-start neg-end)
+      (setq len (length sequence))
+      (when neg-start
+        (setq start (+ len start))
+        (when (or (>= start len)
+                  (< start 0))
+          (funcall signal-fn)))
+      (when neg-end
+        (setq end (+ len end))
+        (when (or (> end len)
+                  (< end 0))
+          (funcall signal-fn))))
+    ;; If we already calculated the length, then we
+    ;; already checked START or END.  If not,
+    ;; then we check whether the nth cdr satisfies
+    ;; START and END.
+    (let ((replaced (nthcdr start sequence)))
+      (if (or (null replaced)
+              (and (null len)
+                   end
+                   (length< replaced (- end start))))
+          (funcall signal-fn)
+        (let ((tag (gensym)))
+          (catch tag
+            (seq-do (if end
+                        (let ((idx start))
+                          (lambda (v)
+                            (if (< idx end)
+                                (setf (car replaced) v
+                                      replaced (cdr replaced)
+                                      idx (1+ idx))
+                              (throw tag nil))))
+                      (lambda (v)
+                        (if replaced
+                            (setf (car replaced) v
+                                  replaced (cdr replaced))
+                          (throw tag nil))))
+                    store))))))
+  store)
+
 \f
 (cl-defgeneric seq-map (function sequence)
   "Return the result of applying FUNCTION to each element of SEQUENCE."
diff --git a/test/lisp/emacs-lisp/seq-tests.el b/test/lisp/emacs-lisp/seq-tests.el
index c06ceb00bdb..d3e46c32f99 100644
--- a/test/lisp/emacs-lisp/seq-tests.el
+++ b/test/lisp/emacs-lisp/seq-tests.el
@@ -312,6 +312,155 @@ test-seq-subseq
                   (:success
                    (should (equal (seq-subseq list start end) res))))))))))))
 
+(cl-defmacro test-setf-seq-subseq-combinations
+    (&key init-vals sub-vals result range error)
+  "Make a test for each combination of sequence type for `seq-subseq' using `setf'.
+
+- INIT-VALS is a list holding the initial elements.
+- RESULT is what the final value should be after substitution.
+- ERROR is whether the form should signal `args-out-of-range'.
+- SUB-VALS is a list holding the elements to be substituted in.
+- RANGE is a list of the `start' and `end' arguments of `seq-subseq'."
+  (let ((tests))
+    (dolist (type1 '(list vector string))
+      (dolist (type2 '(list vector string))
+        (push  (if error
+                   `(should-error (let ((seq (,type1 ,@init-vals)))
+                                    (setf (seq-subseq seq ,@range)
+                                          (,type2 ,@sub-vals))
+                                    seq)
+                                  :type (quote args-out-of-range))
+                 `(should (equal (,type1 ,@result)
+                                 (let ((seq (,type1 ,@init-vals)))
+                                   (setf (seq-subseq seq ,@range)
+                                         (,type2 ,@sub-vals))
+                                   seq))))
+               tests)))
+    `(progn ,@tests)))
+
+(ert-deftest test-setf-seq-subseq ()
+  "Test using `seq-subseq' with `setf'.
+
+Any combination of sequences should work.
+
+An error should be signalled if the inclusive starting index or
+the exclusive ending index is out of the range from 0 through the
+length of the sequence, or if the starting index is greater than
+the ending index.  If the starting index is equal to the ending
+index, then nothing is changed.  If the starting index is equal
+to the length of the sequence, then nothing is changed.  It
+should signal an error in all the cases that `seq-subseq' signals
+an error."
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2)
+   :sub-vals (10 11 12)
+   :range (0)
+   :result (10 11 12))
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1)
+   :sub-vals (10 11 12)
+   :range (0)
+   :result (10 11))
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2)
+   :sub-vals (10 11 12)
+   :range (1)
+   :result (0 10 11))
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2)
+   :sub-vals (10 11 12)
+   :range (1 3)
+   :result (0 10 11))
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2)
+   :sub-vals (10 11 12)
+   :range (3 1)
+   :error t)
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2)
+   :sub-vals (10 11 12)
+   :range (1 100)
+   :error t)
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2)
+   :sub-vals (10 11 12)
+   :range (7)
+   :error t)
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2 3 4)
+   :sub-vals (12 13 14 15)
+   :range (2 3)
+   :result (0 1 12 3 4))
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2 3 4)
+   :sub-vals (12 13 14 15)
+   :range (2 2)
+   :result (0 1 2 3 4))
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2 3 4)
+   :sub-vals (12 13 14 15)
+   :range (5)
+   :result (0 1 2 3 4))
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2 3 4)
+   :sub-vals (12 13 14 15)
+   :range (6)
+   :error t)
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2 3 4)
+   :sub-vals (12 13 14 15)
+   :range (5 6)
+   :error t)
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2 3 4 5 6 7 8 9)
+   :sub-vals (10 11 12 13 14)
+   :range (-2)
+   :result (0 1 2 3 4 5 6 7 10 11))
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2 3 4 5 6 7 8 9)
+   :sub-vals (10 11 12 13 14)
+   :range (-6 -3)
+   :result (0 1 2 3 10 11 12 7 8 9))
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2 3 4 5 6 7 8 9)
+   :sub-vals (10 11 12 13 14)
+   :range (-6 -10)
+   :error t)
+
+  ;; This range might make sense, but since it would signal an error
+  ;; in `seq-subseq', we also signal an error in the `setf' feature.
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2 3 4 5 6 7 8 9)
+   :sub-vals (10 11 12 13 14)
+   :range (-6 0)
+   :error t)
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2 3 4 5 6 7 8 9)
+   :sub-vals (10 11 12 13 14)
+   :range (100)
+   :error t)
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2 3 4 5 6 7 8 9)
+   :sub-vals (10 11 12 13 14)
+   :range (-100)
+   :error t))
+
 (ert-deftest test-seq-concatenate ()
   (with-test-sequences (seq '(2 4 6))
     (should (equal (seq-concatenate 'string seq [8]) (string 2 4 6 8)))
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* bug#68863: Add support for using setf with seq-subseq
  2024-05-14 12:47               ` Okamsn via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2024-05-14 15:52                 ` Michael Heerdegen via Bug reports for GNU Emacs, the Swiss army knife of text editors
  0 siblings, 0 replies; 13+ messages in thread
From: Michael Heerdegen via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-05-14 15:52 UTC (permalink / raw)
  To: Okamsn; +Cc: 68863, eliz, monnier, nicolas

Okamsn <okamsn@protonmail.com> writes:

> Here is what I have thought:
>
> 1. The setter should be consistent for different kinds of sequences. My
> understanding is that arrays cannot be extended, so I think that it
> should not be able to extend lists either. I think that something like a
> non-destructive `seq-replace` or a non-destructive `seq-splice` is a
> separate feature.

[...]

> 3. The setter should not modify the sequence containing the replacement
> values. The manual states: "All functions defined in this library are
> free of side-effects; i.e., they do not modify any sequence (list,
> vector, or string) that you pass as an argument." I think that it makes
> sense for the setter to modify the target sequence, because `setf` is
> used to set places to values, but I think that it would be unexpected to
> modify the sequence of replacement values and that it would be contrary
> to what the manual states and how the other features behave.

That's a bit of a contradiction, since when using your patch we do
modify the original sequence.  That's a reason why your suggestion
doesn't fit that well into the current seq.el.

But The main question for me is: do we want something like you suggest
in seq.el.  And, if we do, should we then provide this functionality as
a generalized variable.  And if we do offer and advertise this
explicitly, it must be implemented as efficient as possible (which is
not the case now).

But I'm stuck with the first question.  And my gut feeling is a clear
"No".  First, I would not provide this as gv but as a normal function.
There is no necessity to go the gv way.

Second, it doesn't fit into seq.el for the reason you gave: the library
promises not to modify sequences.  This is not cut in stone, of course.


> I will try to find uses of `cl-replace` and maybe uses of `append` and
> `nconc` for examples. Also, I have attached a version of the patch that
> does not compute the length of the list unless needed.

I'm mostly interested in real-life examples where your patch would lead
to simpler, better understandable and better maintainable code; and I
think Stefan, too.

That you need such a functionality for your own code counts but is not
enough of its own.  You surely could modify your macro to do what you
want without changing Emacs.


Michael.





^ permalink raw reply	[flat|nested] 13+ messages in thread

end of thread, other threads:[~2024-05-14 15:52 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2024-02-01  3:31 bug#68863: Add support for using setf with seq-subseq Okamsn via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-02-04 18:33 ` bug#68863: [PATCH] " Okamsn via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-02-08 11:39 ` bug#68863: " Eli Zaretskii
2024-02-08 14:25   ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-02-09  3:54     ` Okamsn via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-02-14  2:50       ` Okamsn via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-04-18  2:54         ` Okamsn via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-05-07  1:45           ` Okamsn via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-05-08 21:01             ` Michael Heerdegen via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-05-09 12:16             ` Michael Heerdegen via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-05-09 13:55               ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-05-14 12:47               ` Okamsn via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-05-14 15:52                 ` Michael Heerdegen via Bug reports for GNU Emacs, the Swiss army knife of text editors

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).