Subject: Re: bug#73431: Add `setf` support for `stream.el` in ELPA

From: Michael Heerdegen <[email-masked]>
Date: Sat, 28 Sep 2024 01:55:43 +0200

Hi,

I agree to what Stefan is saying.

Philip Kaludercic <[email-masked]> writes:

> Being able to modify the head of a buffer-stream using setf seems like
> something that could be useful, and certainly more efficient than what
> many people want to do with splitting the return value of
> (buffer-string).

AFAIK, what you normally do in this situation is creating a new stream
reusing the tail of the given one, like in this toy example:

#+begin_src emacs-lisp
(cl-labels ((integers (&optional from)
              (let ((from (or from 1)))
                (stream-cons from (integers (1+ from))))))
  (let ((my-natural-numbers (integers 1))) ;a stream of the natural numbers: 1, 2, ...
    (let ((my-natural-numbers-with-head-replaced ;let's "replace" the first element:
           (stream-cons 'not-1 (stream-rest my-natural-numbers))))
      ;; Test: what are the first 10 elements of this second stream?
      (seq-into
       (seq-subseq my-natural-numbers-with-head-replaced 0 10)
       'list))))
#+end_src

Modifying elements later in the stream conflicts a bit with the idea of
a delayed data structure.  The above idea can be modified to work in
this case too, though.  Creating a new stream even makes more
transparent what is going on...  I don't want to tell anybody how to
work with these guys, but that's how I learned it at university.

In typical scenarios the elements before the one to change already have
been processed and been thrown away, and the element you
actually are interested in is the head element most of the time.  Like
for a queue.

For buffer contents listing streams I could imagine that one could make
such a thing invalidate itself when the buffer has been modified;
like here [I only added few lines to the
(stream ((buffer buffer) &optional pos)) implementation]:

#+begin_src emacs-lisp
(cl-defmethod my-buffer-chars (buffer &optional pos)
  (let ((mods (buffer-modified-tick)))               ; added
    (with-current-buffer buffer
      (unless pos (setq pos (point-min)))
      (if (>= pos (point-max))
          (stream-empty))
      (stream-cons
       (with-current-buffer buffer
         (save-excursion
           (save-restriction
             (widen)
             (goto-char pos)
             (char-after (point)))))
       (if (not (eq mods (buffer-modified-tick)))    ; added
           (error "Buffer has been modified")        ; added
         (my-buffer-chars buffer (1+ pos)))))))
#+end_src

Already generated elements normally can't "invalidate themselves",
unless you make them functions.  But a whole stream that regenerates or
recomputes itself doesn't seem natural to me.  You would rather check
whether the stream is still valid _explicitly_ - and if not, just call
the function that returned that stream (most of the time a named
function, like above) again to create a new stream - in the above
example, possibly with an adopted POS argument.

My two cents.

Michael.