unofficial mirror of emacs-devel@gnu.org 
 help / color / mirror / code / Atom feed
* Is this a bug in while-let or do I missunderstand it?
@ 2024-11-08 16:25 arthur miller
  2024-11-08 19:23 ` Philip Kaludercic
  2024-11-09  9:29 ` Yuri Khan
  0 siblings, 2 replies; 59+ messages in thread
From: arthur miller @ 2024-11-08 16:25 UTC (permalink / raw)
  To: emacs-devel@gnu.org

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

(progn
  (while-let ((run t))
    (message "Running")
    (setf run nil))
  (message "out of loop"))

It ends in infinite recursion. setf/setq have no effect on the lexical variable.

I tooka look, but I don't understand why is it necessary to build while-let  on
if-let. This simplified version did it for me:

(defmacro while-let (spec &rest body)
  "Bind variables according to SPEC and conditionally evaluate BODY.
Evaluate each binding in turn, stopping if a binding value is nil.
If all bindings are non-nil, eval BODY and repeat.

The variable list SPEC is the same as in `if-let*'."
  (declare (indent 1) (debug if-let))
  (let* ((bindings (if (and (consp spec) (symbolp (car spec)))
                           (list spec)
                         spec))
         (variables (mapcar #'car bindings)))
    `(let* ,bindings
       (while (and ,@variables)
         ,@body))))

(progn
  (while-let ((run t))
    (message "Running")
    (setf run nil))
  (message "out of loop"))  => "out of loop"

Or did I missunderstood how to use while-let in subr.el?

[-- Attachment #2: Type: text/html, Size: 5636 bytes --]

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

* Re: Is this a bug in while-let or do I missunderstand it?
  2024-11-08 16:25 Is this a bug in while-let or do I missunderstand it? arthur miller
@ 2024-11-08 19:23 ` Philip Kaludercic
  2024-11-09  3:30   ` Sv: " arthur miller
  2024-11-09  9:29 ` Yuri Khan
  1 sibling, 1 reply; 59+ messages in thread
From: Philip Kaludercic @ 2024-11-08 19:23 UTC (permalink / raw)
  To: arthur miller; +Cc: emacs-devel@gnu.org

arthur miller <arthur.miller@live.com> writes:

> (progn
>   (while-let ((run t))
>     (message "Running")
>     (setf run nil))
>   (message "out of loop"))
>
> It ends in infinite recursion. setf/setq have no effect on the lexical variable.
>
> I tooka look, but I don't understand why is it necessary to build while-let  on
> if-let. This simplified version did it for me:
>
> (defmacro while-let (spec &rest body)
>   "Bind variables according to SPEC and conditionally evaluate BODY.
> Evaluate each binding in turn, stopping if a binding value is nil.
> If all bindings are non-nil, eval BODY and repeat.
>
> The variable list SPEC is the same as in `if-let*'."
>   (declare (indent 1) (debug if-let))
>   (let* ((bindings (if (and (consp spec) (symbolp (car spec)))
>                            (list spec)
>                          spec))
>          (variables (mapcar #'car bindings)))
>     `(let* ,bindings
>        (while (and ,@variables)
>          ,@body))))

With `if-let*' or `while-let' you want to have a sequence of
computations that are evaluated in order (either once for `if-let*' or
for every iteration in the case of `while-let'), until at least one
evaluates to nil.  All subsequent bindings shouldn't be evaluated, as
would be the case with your version of the macro.

> (progn
>   (while-let ((run t))
>     (message "Running")
>     (setf run nil))
>   (message "out of loop"))  => "out of loop"
>
> Or did I missunderstood how to use while-let in subr.el?

-- 
	Philip Kaludercic on siskin



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

* Sv: Is this a bug in while-let or do I missunderstand it?
  2024-11-08 19:23 ` Philip Kaludercic
@ 2024-11-09  3:30   ` arthur miller
  0 siblings, 0 replies; 59+ messages in thread
From: arthur miller @ 2024-11-09  3:30 UTC (permalink / raw)
  To: Philip Kaludercic; +Cc: emacs-devel@gnu.org

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

>> (progn
>>   (while-let ((run t))
>>     (message "Running")
>>     (setf run nil))
>>   (message "out of loop"))
>>
>> It ends in infinite recursion. setf/setq have no effect on the lexical variable.
>>
>> I tooka look, but I don't understand why is it necessary to build while-let  on
>> if-let. This simplified version did it for me:
>>
>> (defmacro while-let (spec &rest body)
>>   "Bind variables according to SPEC and conditionally evaluate BODY.
>> Evaluate each binding in turn, stopping if a binding value is nil.
>> If all bindings are non-nil, eval BODY and repeat.
>>
>> The variable list SPEC is the same as in `if-let*'."
>>   (declare (indent 1) (debug if-let))
>>   (let* ((bindings (if (and (consp spec) (symbolp (car spec)))
>>                            (list spec)
>>                          spec))
>>          (variables (mapcar #'car bindings)))
>>     `(let* ,bindings
>>        (while (and ,@variables)
>>          ,@body))))
>
>With `if-let*' or `while-let' you want to have a sequence of
>computations that are evaluated in order (either once for `if-let*' or
>for every iteration in the case of `while-let'), until at least one
>evaluates to nil.  All subsequent bindings shouldn't be evaluated, as
>would be the case with your version of the macro.

Aha, that sounds like you want to optimize the evaluation of bindings
*before* the body is run? I think you should first generate correct
code, than optimize. As it is now, the while-let ends up in an endless loop.
Setting the lexical variable have no effect at all (with lexical binding
on). Simple macroexpand shows why:

(catch 'done918
  (while t
    (let* ((run (and t t)))
      (if run (progn (message "running") (setq run nil)) (throw 'done918 nil)))))

Problem is your if-let*:

(catch 'done917
  (while t
    (if-let* ((run t)) (progn (message "running") (setf run nil))
      (throw 'done917 nil))))

Furthermore, considering what you wrote in the answer; your if-let
will not do what you think, and shortcut evaluation of bindings, it
will do exactly the same as what I do in while-let:

(while-let ((x 1)
            (run t))
            (message "running")
            (setf run nil))

=> (catch 'done923
  (while t
    (let* ((x (and t 1)) (run (and x t)))
      (if run (progn (message "running") (setq run nil)) (throw 'done923 nil)))))

As you see from the macro expansion, your if-let* has expanded
to a let* which also evaluates all of the bindings before it runs the program.

The problem/bug is in if-expression. It 'ands' next value with the previous
value and 't, instead of previous value and it itself (I think that was the idea).
 If you fix that, I think it could work in terms of correctness, but it wold still
 evaluate all of the bindings.

To illustrate it better, we can change while-let loop and add one extra variable:

(while-let ((run t)
            (x 1))
  (message "running")
  (setf run nil))

=> (catch 'done929
    (while t
      (let* ((run (and t t)) (x (and run 1)))
        (if x (progn (message "running") (setq run nil)) (throw 'done929 nil)))))

If you want to optimize evaluation of bindings, I think one way would be to
generate a cascading let bindings, one per each binding, do a test and throw
your 'done nil if binding evaluated to nil. Perhaps there is some other way too,
I don't know, that was just the first idea.

I am doing this from igc branch, but I don't think that should matter.

However, I wonder if/how the author has tested this? I don't see any tests i
subr-tests.el for if-let, when-let and while-let. Not trying to be rude or
impolite, just an observation; I was doing simple comparison with a different
syntax for let-forms, and discovered that on the first try with a simplest example.

I suggest to do the trivial implementation of if-let, when-let and while-let
as shown in my first example until you get the optimized version. And you can
even have ordinary and star-versions of each form trivially. Just a suggestion of
course.

best regards
/arthur

________________________________
Från: Philip Kaludercic <philipk@posteo.net>
Skickat: den 8 november 2024 20:23
Till: arthur miller <arthur.miller@live.com>
Kopia: emacs-devel@gnu.org <emacs-devel@gnu.org>
Ämne: Re: Is this a bug in while-let or do I missunderstand it?

arthur miller <arthur.miller@live.com> writes:

> (progn
>   (while-let ((run t))
>     (message "Running")
>     (setf run nil))
>   (message "out of loop"))
>
> It ends in infinite recursion. setf/setq have no effect on the lexical variable.
>
> I tooka look, but I don't understand why is it necessary to build while-let  on
> if-let. This simplified version did it for me:
>
> (defmacro while-let (spec &rest body)
>   "Bind variables according to SPEC and conditionally evaluate BODY.
> Evaluate each binding in turn, stopping if a binding value is nil.
> If all bindings are non-nil, eval BODY and repeat.
>
> The variable list SPEC is the same as in `if-let*'."
>   (declare (indent 1) (debug if-let))
>   (let* ((bindings (if (and (consp spec) (symbolp (car spec)))
>                            (list spec)
>                          spec))
>          (variables (mapcar #'car bindings)))
>     `(let* ,bindings
>        (while (and ,@variables)
>          ,@body))))

With `if-let*' or `while-let' you want to have a sequence of
computations that are evaluated in order (either once for `if-let*' or
for every iteration in the case of `while-let'), until at least one
evaluates to nil.  All subsequent bindings shouldn't be evaluated, as
would be the case with your version of the macro.

> (progn
>   (while-let ((run t))
>     (message "Running")
>     (setf run nil))
>   (message "out of loop"))  => "out of loop"
>
> Or did I missunderstood how to use while-let in subr.el?

--
        Philip Kaludercic on siskin

[-- Attachment #2: Type: text/html, Size: 19912 bytes --]

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

* Re: Is this a bug in while-let or do I missunderstand it?
  2024-11-08 16:25 Is this a bug in while-let or do I missunderstand it? arthur miller
  2024-11-08 19:23 ` Philip Kaludercic
@ 2024-11-09  9:29 ` Yuri Khan
  2024-11-09 13:03   ` Sv: " arthur miller
  1 sibling, 1 reply; 59+ messages in thread
From: Yuri Khan @ 2024-11-09  9:29 UTC (permalink / raw)
  To: arthur miller; +Cc: emacs-devel@gnu.org

On Sat, 9 Nov 2024 at 01:44, arthur miller <arthur.miller@live.com> wrote:
>
> (progn
>   (while-let ((run t))
>     (message "Running")
>     (setf run nil))
>   (message "out of loop"))
>
> It ends in infinite recursion. setf/setq have no effect on the lexical variable.

Probably not infinite recursion but infinite loop.

Why would you expect anything else? ‘while-let’ is documented as:

    Bind variables according to SPEC and conditionally evaluate BODY.
    Evaluate each binding in turn, stopping if a binding value is nil.
    If all bindings are non-nil, eval BODY and repeat.

In your case, the sequence is:

1. Evaluate the expression ‘t’ in the first (and only) binding. This yields ‘t’.
2. Bind the result of step 1, ‘t’, to local variable ‘run’.
3. Check if ‘run’ is nil. It isn’t, so proceed to step 4.
4. There are no more bindings. Run the body.
4a. Evaluate ‘(message "Running")’.
4b. Evaluate ‘(setf run nil)’. This changes the value of ‘run’ to nil.
5. Repeat from step 1.

Re-evaluating every binding’s expression is expected. Consider this usage:

    (while-let ((values-to-process (get-values)))
      (while values-to-process
        (setq value (pop values-to-process))
        (process value)))

At the end of each outer loop’s iteration, ‘values-to-process’ is nil,
but you don’t want to break out of the outer loop immediately, you
want to check if there is more work to do.



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

* Sv: Is this a bug in while-let or do I missunderstand it?
  2024-11-09  9:29 ` Yuri Khan
@ 2024-11-09 13:03   ` arthur miller
  2024-11-09 13:15     ` Yuri Khan
  0 siblings, 1 reply; 59+ messages in thread
From: arthur miller @ 2024-11-09 13:03 UTC (permalink / raw)
  To: Yuri Khan; +Cc: emacs-devel@gnu.org

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

>> (progn
>>   (while-let ((run t))
>>     (message "Running")
>>     (setf run nil))
>>   (message "out of loop"))
>>
>> It ends in infinite recursion. setf/setq have no effect on the lexical variable.
>
>Probably not infinite recursion but infinite loop.
>
>Why would you expect anything else? ‘while-let’ is documented as:
>
>    Bind variables according to SPEC and conditionally evaluate BODY.

What should I expect?

It does not says *read-only bindings*, it says bindings. Is it
unreasonable to store a value in an established lexical binding?

(progn
  (let ((run t))
    (while run
      (message "running")
      (setf run nil))
    (message "not running")))

That is what I expect while-let to be equivalent to. But in practice
introduced bindings are "read only" since the current implementation of
while-let throws out bindings on each iteration of while loop, which
results in bindings being reset.

If that was the intention, I think it is counterintuitive, but I doubt it is.

[-- Attachment #2: Type: text/html, Size: 4876 bytes --]

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

* Re: Is this a bug in while-let or do I missunderstand it?
  2024-11-09 13:03   ` Sv: " arthur miller
@ 2024-11-09 13:15     ` Yuri Khan
  2024-11-09 13:38       ` Sv: " arthur miller
  0 siblings, 1 reply; 59+ messages in thread
From: Yuri Khan @ 2024-11-09 13:15 UTC (permalink / raw)
  To: arthur miller; +Cc: emacs-devel@gnu.org

On Sat, 9 Nov 2024 at 20:03, arthur miller <arthur.miller@live.com> wrote:
>
> >> (progn
> >>   (while-let ((run t))
> >>     (message "Running")
> >>     (setf run nil))
> >>   (message "out of loop"))
> >>
> >> It ends in infinite recursion. setf/setq have no effect on the lexical variable.
> >
> >Probably not infinite recursion but infinite loop.
> >
> >Why would you expect anything else? ‘while-let’ is documented as:
> >
> >    Bind variables according to SPEC and conditionally evaluate BODY.
>
> What should I expect?
>
> It does not says *read-only bindings*, it says bindings. Is it
> unreasonable to store a value in an established lexical binding?

I expect the binding is writable *but* it gets re-assigned on each iteration.

> (progn
>   (let ((run t))
>     (while run
>       (message "running")
>       (setf run nil))
>     (message "not running")))
>
> That is what I expect while-let to be equivalent to.

This is what I expect:

    (progn
      (let ((run))
        (while (setf run t)
          (message "running")
          (setf run nil)       ; useless because ‘run’ will be
reassigned right next
        )
      (message "not running")  ; unreachable
    )



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

* Sv: Is this a bug in while-let or do I missunderstand it?
  2024-11-09 13:15     ` Yuri Khan
@ 2024-11-09 13:38       ` arthur miller
  2024-11-09 13:41         ` Yuri Khan
  0 siblings, 1 reply; 59+ messages in thread
From: arthur miller @ 2024-11-09 13:38 UTC (permalink / raw)
  To: Yuri Khan; +Cc: emacs-devel@gnu.org

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

>> >> (progn
>> >>   (while-let ((run t))
>> >>     (message "Running")
>> >>     (setf run nil))
>> >>   (message "out of loop"))
>> >>
>> >> It ends in infinite recursion. setf/setq have no effect on the lexical variable.
>> >
>> >Probably not infinite recursion but infinite loop.
>> >
>> >Why would you expect anything else? ‘while-let’ is documented as:
>> >
>> >    Bind variables according to SPEC and conditionally evaluate BODY.
>>
>> What should I expect?
>>
>> It does not says *read-only bindings*, it says bindings. Is it
>> unreasonable to store a value in an established lexical binding?
>
>I expect the binding is writable *but* it gets re-assigned on each iteration.

Yes.

>> That is what I expect while-let to be equivalent to.
>
>This is what I expect:
>
>    (progn
>      (let ((run))
>        (while (setf run t)
>          (message "running")
>          (setf run nil)       ; useless because ‘run’ will be
>reassigned right next
>        )
>      (message "not running")  ; unreachable
>    )
>

Mnjah; more like this:

(catch 'done
  (while t
    (let* ((run nil))
      (if run
          (do-body)
          (throw 'done nil)))))

I have already posted the macro expansions in respone to Phillip.
It is quite clear what is going on. I think it is a bug, or at
least very unintuitive behaviour. But the worst, we can see that the
claimed optimizaiton does not take place at all:

(pp (macroexpand-all
     '(while-let ((run t)
                  (x 'expensive)
                  (y 'more-expensive)
                  (z 'the-most-expensive))
        (message "running")
        (setf run nil))))

(catch 'done1522
  (while t
    (let*
        ((run (and t t)) (x (and run 'expensive)) (y (and x 'more-expensive))
         (z (and y 'the-most-expensive)))
      (if z (progn (message "running") (setq run nil)) (throw 'done1522 nil)))))

Which makes wonder if the convoluted code in subr.el is worth compared to the
naive implementation I posted. Perhaps someone can pull off the optimization with
 some clever macro, I don't know.

I think it was enough from me as an outsider to point out the possible bug. Whether
 people here wants to poop on it, or acknowledge and fix the bug is not up to me.

In other words, I think I am done here.

/best regards
________________________________
Från: Yuri Khan <yuri.v.khan@gmail.com>
Skickat: den 9 november 2024 14:15
Till: arthur miller <arthur.miller@live.com>
Kopia: emacs-devel@gnu.org <emacs-devel@gnu.org>
Ämne: Re: Is this a bug in while-let or do I missunderstand it?

On Sat, 9 Nov 2024 at 20:03, arthur miller <arthur.miller@live.com> wrote:
>
> >> (progn
> >>   (while-let ((run t))
> >>     (message "Running")
> >>     (setf run nil))
> >>   (message "out of loop"))
> >>
> >> It ends in infinite recursion. setf/setq have no effect on the lexical variable.
> >
> >Probably not infinite recursion but infinite loop.
> >
> >Why would you expect anything else? ‘while-let’ is documented as:
> >
> >    Bind variables according to SPEC and conditionally evaluate BODY.
>
> What should I expect?
>
> It does not says *read-only bindings*, it says bindings. Is it
> unreasonable to store a value in an established lexical binding?

I expect the binding is writable *but* it gets re-assigned on each iteration.

> (progn
>   (let ((run t))
>     (while run
>       (message "running")
>       (setf run nil))
>     (message "not running")))
>
> That is what I expect while-let to be equivalent to.

This is what I expect:

    (progn
      (let ((run))
        (while (setf run t)
          (message "running")
          (setf run nil)       ; useless because ‘run’ will be
reassigned right next
        )
      (message "not running")  ; unreachable
    )

[-- Attachment #2: Type: text/html, Size: 14580 bytes --]

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

* Re: Is this a bug in while-let or do I missunderstand it?
  2024-11-09 13:38       ` Sv: " arthur miller
@ 2024-11-09 13:41         ` Yuri Khan
  2024-11-09 13:47           ` Sv: " arthur miller
  0 siblings, 1 reply; 59+ messages in thread
From: Yuri Khan @ 2024-11-09 13:41 UTC (permalink / raw)
  To: arthur miller; +Cc: emacs-devel@gnu.org

On Sat, 9 Nov 2024 at 20:38, arthur miller <arthur.miller@live.com> wrote:

> >I expect the binding is writable *but* it gets re-assigned on each iteration.
>
> Yes.
> I have already posted the macro expansions in respone to Phillip.
> It is quite clear what is going on. I think it is a bug, or at
> least very unintuitive behaviour.

Why? Pretty much all implementations of the ‘while’ loop in all
languages I’ve seen re-evaluate the condition on every iteration.



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

* Sv: Is this a bug in while-let or do I missunderstand it?
  2024-11-09 13:41         ` Yuri Khan
@ 2024-11-09 13:47           ` arthur miller
  2024-11-09 14:04             ` Yuri Khan
  2024-11-09 21:47             ` Sv: " Joost Kremers
  0 siblings, 2 replies; 59+ messages in thread
From: arthur miller @ 2024-11-09 13:47 UTC (permalink / raw)
  To: Yuri Khan; +Cc: emacs-devel@gnu.org

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

>> least very unintuitive behaviour.
>
>Why? Pretty much all implementations of the ‘while’ loop in all
>languages I’ve seen re-evaluate the condition on every iteration.

That wasn't the un-intuitive part :-).

If it wasn't clear, the unintuitive part is that while-let was to
establish the local environment, so that we don't need to type:

(let ((som-var (init-form)))
    (while some-var
        ... ))

At least is how I understand the purpose of if-let, when-let and while-let.
________________________________
Från: Yuri Khan <yuri.v.khan@gmail.com>
Skickat: den 9 november 2024 14:41
Till: arthur miller <arthur.miller@live.com>
Kopia: emacs-devel@gnu.org <emacs-devel@gnu.org>
Ämne: Re: Is this a bug in while-let or do I missunderstand it?

On Sat, 9 Nov 2024 at 20:38, arthur miller <arthur.miller@live.com> wrote:

> >I expect the binding is writable *but* it gets re-assigned on each iteration.
>
> Yes.
> I have already posted the macro expansions in respone to Phillip.
> It is quite clear what is going on. I think it is a bug, or at
> least very unintuitive behaviour.

Why? Pretty much all implementations of the ‘while’ loop in all
languages I’ve seen re-evaluate the condition on every iteration.

[-- Attachment #2: Type: text/html, Size: 3612 bytes --]

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

* Re: Is this a bug in while-let or do I missunderstand it?
  2024-11-09 13:47           ` Sv: " arthur miller
@ 2024-11-09 14:04             ` Yuri Khan
  2024-11-09 14:44               ` Sv: " arthur miller
  2024-11-09 16:33               ` Alfred M. Szmidt
  2024-11-09 21:47             ` Sv: " Joost Kremers
  1 sibling, 2 replies; 59+ messages in thread
From: Yuri Khan @ 2024-11-09 14:04 UTC (permalink / raw)
  To: arthur miller; +Cc: emacs-devel@gnu.org

On Sat, 9 Nov 2024 at 20:47, arthur miller <arthur.miller@live.com> wrote:

> If it wasn't clear, the unintuitive part is that while-let was to
> establish the local environment, so that we don't need to type:
>
> (let ((som-var (init-form)))
>     (while some-var
>         ... ))

But if it did it that way, the condition (init-form) would only be
evaluated once, and I’d find *that* counterintuitive. Consider the
usual form of a while loop:

    (while-let ((run (some-condition)))
      (message "running"))

Do you expect that to evaluate (some-condition) once, then, if it’s
initially true, run forever?



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

* Sv: Is this a bug in while-let or do I missunderstand it?
  2024-11-09 14:04             ` Yuri Khan
@ 2024-11-09 14:44               ` arthur miller
  2024-11-09 16:33               ` Alfred M. Szmidt
  1 sibling, 0 replies; 59+ messages in thread
From: arthur miller @ 2024-11-09 14:44 UTC (permalink / raw)
  To: Yuri Khan; +Cc: emacs-devel@gnu.org

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

>But if it did it that way, the condition (init-form) would only be
>evaluated once, and I’d find *that* counterintuitive. Consider the

?

I don't know man. Why would it be so? Loops conditions should always be
evaluated in a loop, otherwise how should loop terminate? But I don't
expect a loop to re-initiate all bindings from defaults each time it
runs an iteration. It is both expensive, unnecessary and counterintuitive.
Don't you think?

>usual form of a while loop:
>
>    (while-let ((run (some-condition)))
>      (message "running"))
>
>Do you expect that to evaluate (some-condition) once, then, if it’s
>initially true, run forever?

That usual form of while loop works because (some-condition) is a function,
and it obviously computes its condition based on the external environment.

Perhaps you can set some global value in the loop to stop the evaluation, or
perhaps (some-condition) computes the terminating condition based on some
other volatile values from the environment. I don't see is that in the
conflict to what I say.

If you instead write

(while-let ((run some-condition))
   (message "running"))

Than yes, the loop will not terminate unless you terminate it from the loop.
However, since we are throwing away the bindings on each iteration, we are
also throwing away the previously computed binding, we can't write:

(while-let ((run some-condition))
   (message "running")
   (setf run nil))

Which to me seems counterintuitive.
________________________________
Från: Yuri Khan <yuri.v.khan@gmail.com>
Skickat: den 9 november 2024 15:04
Till: arthur miller <arthur.miller@live.com>
Kopia: emacs-devel@gnu.org <emacs-devel@gnu.org>
Ämne: Re: Is this a bug in while-let or do I missunderstand it?

On Sat, 9 Nov 2024 at 20:47, arthur miller <arthur.miller@live.com> wrote:

> If it wasn't clear, the unintuitive part is that while-let was to
> establish the local environment, so that we don't need to type:
>
> (let ((som-var (init-form)))
>     (while some-var
>         ... ))

But if it did it that way, the condition (init-form) would only be
evaluated once, and I’d find *that* counterintuitive. Consider the
usual form of a while loop:

    (while-let ((run (some-condition)))
      (message "running"))

Do you expect that to evaluate (some-condition) once, then, if it’s
initially true, run forever?

[-- Attachment #2: Type: text/html, Size: 7708 bytes --]

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

* Re: Is this a bug in while-let or do I missunderstand it?
  2024-11-09 14:04             ` Yuri Khan
  2024-11-09 14:44               ` Sv: " arthur miller
@ 2024-11-09 16:33               ` Alfred M. Szmidt
  2024-11-09 16:44                 ` Eli Zaretskii
  2024-11-09 20:29                 ` Sv: " arthur miller
  1 sibling, 2 replies; 59+ messages in thread
From: Alfred M. Szmidt @ 2024-11-09 16:33 UTC (permalink / raw)
  To: Yuri Khan; +Cc: arthur.miller, emacs-devel

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1: Type: text/plain, Size: 1442 bytes --]

   > If it wasn't clear, the unintuitive part is that while-let was to
   > establish the local environment, so that we don't need to type:
   >
   > (let ((som-var (init-form)))
   >     (while some-var
   >         ... ))

   But if it did it that way, the condition (init-form) would only be
   evaluated once, and I’d find *that* counterintuitive. Consider the
   usual form of a while loop:

       (while-let ((run (some-condition)))
	 (message "running"))

   Do you expect that to evaluate (some-condition) once, then, if it’s
   initially true, run forever?

That is how it is described in the manual, so yes (some-condition)
should only be done once, and not every iteration.  See (elisp)
Conditionals .

      It can be convenient to bind variables in conjunction with using a
   conditional.  It's often the case that you compute a value, and then
   want to do something with that value if it's non-‘nil’.  The
   straightforward way to do that is to just write, for instance:
   
        (let ((result1 (do-computation)))
          (when result1
            (let ((result2 (do-more result1)))
              (when result2
                (do-something result2)))))

      Since this is a very common pattern, Emacs provides a number of
   macros to make this easier and more readable.  The above can be written
   the following way instead:

... following the various with various FOO-let forms, ending with
while-let.



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

* Re: Is this a bug in while-let or do I missunderstand it?
  2024-11-09 16:33               ` Alfred M. Szmidt
@ 2024-11-09 16:44                 ` Eli Zaretskii
  2024-11-09 16:53                   ` Eli Zaretskii
                                     ` (3 more replies)
  2024-11-09 20:29                 ` Sv: " arthur miller
  1 sibling, 4 replies; 59+ messages in thread
From: Eli Zaretskii @ 2024-11-09 16:44 UTC (permalink / raw)
  To: Alfred M. Szmidt; +Cc: yuri.v.khan, arthur.miller, emacs-devel

> From: "Alfred M. Szmidt" <ams@gnu.org>
> Cc: arthur.miller@live.com, emacs-devel@gnu.org
> Date: Sat, 09 Nov 2024 11:33:45 -0500
> 
>        (while-let ((run (some-condition)))
> 	 (message "running"))
> 
>    Do you expect that to evaluate (some-condition) once, then, if it’s
>    initially true, run forever?
> 
> That is how it is described in the manual, so yes (some-condition)
> should only be done once, and not every iteration.  See (elisp)
> Conditionals .

Which could mean that the manual is wrong and needs to be fixed.

>       It can be convenient to bind variables in conjunction with using a
>    conditional.  It's often the case that you compute a value, and then
>    want to do something with that value if it's non-‘nil’.  The
>    straightforward way to do that is to just write, for instance:
>    
>         (let ((result1 (do-computation)))
>           (when result1
>             (let ((result2 (do-more result1)))
>               (when result2
>                 (do-something result2)))))
> 
>       Since this is a very common pattern, Emacs provides a number of
>    macros to make this easier and more readable.  The above can be written
>    the following way instead:
> 
> ... following the various with various FOO-let forms, ending with
> while-let.

The above description actually supports what Yuri was saying, not what
Arthur and you expect.



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

* Re: Is this a bug in while-let or do I missunderstand it?
  2024-11-09 16:44                 ` Eli Zaretskii
@ 2024-11-09 16:53                   ` Eli Zaretskii
  2024-11-09 17:33                   ` Andreas Schwab
                                     ` (2 subsequent siblings)
  3 siblings, 0 replies; 59+ messages in thread
From: Eli Zaretskii @ 2024-11-09 16:53 UTC (permalink / raw)
  To: ams; +Cc: yuri.v.khan, arthur.miller, emacs-devel

> Date: Sat, 09 Nov 2024 18:44:28 +0200
> From: Eli Zaretskii <eliz@gnu.org>
> Cc: yuri.v.khan@gmail.com, arthur.miller@live.com, emacs-devel@gnu.org
> 
> > From: "Alfred M. Szmidt" <ams@gnu.org>
> > Cc: arthur.miller@live.com, emacs-devel@gnu.org
> > Date: Sat, 09 Nov 2024 11:33:45 -0500
> > 
> >        (while-let ((run (some-condition)))
> > 	 (message "running"))
> > 
> >    Do you expect that to evaluate (some-condition) once, then, if it’s
> >    initially true, run forever?
> > 
> > That is how it is described in the manual, so yes (some-condition)
> > should only be done once, and not every iteration.  See (elisp)
> > Conditionals .
> 
> Which could mean that the manual is wrong and needs to be fixed.

The manual might be not explicit enough about this aspect, but it does
say "repeat until a binding in SPEC is ‘nil’", which at least hints
that SPEC is re-evaluated on each iteration.  Because otherwise
repeatedly testing the same result and hoping it will become nil would
be tantamount to the proverbial definition of insanity.



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

* Re: Is this a bug in while-let or do I missunderstand it?
  2024-11-09 16:44                 ` Eli Zaretskii
  2024-11-09 16:53                   ` Eli Zaretskii
@ 2024-11-09 17:33                   ` Andreas Schwab
  2024-11-09 18:07                   ` [External] : " Drew Adams
  2024-11-14 21:50                   ` John ff
  3 siblings, 0 replies; 59+ messages in thread
From: Andreas Schwab @ 2024-11-09 17:33 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: Alfred M. Szmidt, yuri.v.khan, arthur.miller, emacs-devel

On Nov 09 2024, Eli Zaretskii wrote:

>>       It can be convenient to bind variables in conjunction with using a
>>    conditional.  It's often the case that you compute a value, and then
>>    want to do something with that value if it's non-‘nil’.  The
>>    straightforward way to do that is to just write, for instance:
>>    
>>         (let ((result1 (do-computation)))
>>           (when result1
>>             (let ((result2 (do-more result1)))
>>               (when result2
>>                 (do-something result2)))))
>> 
>>       Since this is a very common pattern, Emacs provides a number of
>>    macros to make this easier and more readable.  The above can be written
>>    the following way instead:
>> 
>> ... following the various with various FOO-let forms, ending with
>> while-let.
>
> The above description actually supports what Yuri was saying, not what
> Arthur and you expect.

The description only talks about when, where the condition is only
evaluated once not matter what.  If you replace when with while you have
a very different situation.

-- 
Andreas Schwab, schwab@linux-m68k.org
GPG Key fingerprint = 7578 EB47 D4E5 4D69 2510  2552 DF73 E780 A9DA AEC1
"And now for something completely different."



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

* RE: [External] : Re: Is this a bug in while-let or do I missunderstand it?
  2024-11-09 16:44                 ` Eli Zaretskii
  2024-11-09 16:53                   ` Eli Zaretskii
  2024-11-09 17:33                   ` Andreas Schwab
@ 2024-11-09 18:07                   ` Drew Adams
  2024-11-09 18:18                     ` Alfred M. Szmidt
  2024-11-09 19:32                     ` Sv: [External] : Re: Is this a bug in while-let or do I missunderstand it? arthur miller
  2024-11-14 21:50                   ` John ff
  3 siblings, 2 replies; 59+ messages in thread
From: Drew Adams @ 2024-11-09 18:07 UTC (permalink / raw)
  To: Eli Zaretskii, Alfred M. Szmidt
  Cc: yuri.v.khan@gmail.com, arthur.miller@live.com,
	emacs-devel@gnu.org

IMHO, this is a problem with all of the
if/and/when/while-let[*] thingies.  If someone
uses them a lot then she probably knows what
goes on, in what sequence.  But a priori it's
not so clear.

This may just mean that the doc needs to take
pains to be very clear, maybe even with examples
or by showing a macro expansion explicitly.

Using catch/throw, let[*], and/if/when/while
together is always _clearer_, IMO.  And it's
often no more verbose.  Witness all of the
discussion about <X>-let* versus <X>-let names
and this current discussion.  The name itself
doesn't clearly tell you what it does... which
is OK, but only if the doc tells you that
clearly.

I'm not saying no one should use, or Elisp
shouldn't provide, if/and/when/while-let[*]
thingies.  I'm just saying (1) I don't find
them helpful, personally (I don't use them),
and (more importantly) (2) if we provide them
then their doc needs to be very specific about
what _exactly_ they do, and when (if not also
how).

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

* Re: [External] : Re: Is this a bug in while-let or do I missunderstand it?
  2024-11-09 18:07                   ` [External] : " Drew Adams
@ 2024-11-09 18:18                     ` Alfred M. Szmidt
  2024-11-09 20:02                       ` Jens Schmidt
  2024-11-09 19:32                     ` Sv: [External] : Re: Is this a bug in while-let or do I missunderstand it? arthur miller
  1 sibling, 1 reply; 59+ messages in thread
From: Alfred M. Szmidt @ 2024-11-09 18:18 UTC (permalink / raw)
  To: Drew Adams; +Cc: eliz, yuri.v.khan, arthur.miller, emacs-devel

   I'm not saying no one should use, or Elisp
   shouldn't provide, if/and/when/while-let[*]
   thingies.  I'm just saying (1) I don't find
   them helpful, personally (I don't use them),
   and (more importantly) (2) if we provide them
   then their doc needs to be very specific about
   what _exactly_ they do, and when (if not also
   how).

Agreed.  I was looking of the usages of WHILE-LET in Emacs, and each
time it is used it is quite confusing.  For example this:

      (while-let ((b)
                  ((< b end))
                  (e (next-single-property-change (1+ b) 'erc--msg nil end)))
        (save-restriction
          (narrow-to-region b e)
          (funcall fn))
        (setq b e))


If the bindings are to be reevaluated on each iteration, shouldn't B
always be NIL, and that it would end up with (< NIL end) would be on
each iteration causing an error?

How can (< b end) even be a spec binding here -- shouldn't that be an
error?

The exapanded code looks like this:

(catch 'done39
  (while t
    (let* ((s (and t b))
	   (s (and s (< b end)))
	   (e (and s (next-single-property-change ... ... nil end))))
      (if e
	  (progn
	    (save-restriction (narrow-to-region b e) (funcall fn))
	    (setq b e))
	(throw 'done39 nil)))))



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

* Sv: [External] : Re: Is this a bug in while-let or do I missunderstand it?
  2024-11-09 18:07                   ` [External] : " Drew Adams
  2024-11-09 18:18                     ` Alfred M. Szmidt
@ 2024-11-09 19:32                     ` arthur miller
  2024-11-09 22:36                       ` Drew Adams
  1 sibling, 1 reply; 59+ messages in thread
From: arthur miller @ 2024-11-09 19:32 UTC (permalink / raw)
  To: Drew Adams, Eli Zaretskii, Alfred M. Szmidt
  Cc: yuri.v.khan@gmail.com, emacs-devel@gnu.org

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

>IMHO, this is a problem with all of the
>if/and/when/while-let[*] thingies.

Only while-let.

>uses them a lot then she probably knows what
>goes on, in what sequence.  But a priori it's
>not so clear.
>
>This may just mean that the doc needs to take
>pains to be very clear, maybe even with examples
>or by showing a macro expansion explicitly.
>
>Using catch/throw, let[*], and/if/when/while
>together is always _clearer_, IMO.  And it's
>often no more verbose.  Witness all of the

I don't think so. As much as I have understood
if-let, when-let and unique for elisp while-let,
are an "informal formalization", or just a shorthand
for following idiom:

(let ((some-var init-form))
  (if some-var than-form else-form))

Similar for when-let. Consider also a bigger piece
of code, where you have longer let-form, and want
to do some loop:

(let ((var1 init1)
      ...
      (varN initN)
      (loop-invarant init-loop-invariang))

  ....
  (while loop-invariant
    ...)

 ...)

In this context you are leaking loop-invariant in the
entire scope of enclosing let-form, whereas

(let ((var1 init1)
      ...
      (varN initN))

  ....
  (while ((loop-invariant init-loop-invariant))
    ...)

 ...)

limits 'loop-invariant' to the lexical environment
of while loop. You can compare this to pre- C++11
standards where compilers would leak loop invariants
after the scope of the loop:

   for (int i=0; i<10; i++) {
      ...
   }

   i = 2; <-- i visible outside of the for-loop scope

Limiting scope of variables to the lexical scope
where they matter helps to minimize accidental
usages, and also make code easier to read and understand.
________________________________
Från: Drew Adams <drew.adams@oracle.com>
Skickat: den 9 november 2024 19:07
Till: Eli Zaretskii <eliz@gnu.org>; Alfred M. Szmidt <ams@gnu.org>
Kopia: yuri.v.khan@gmail.com <yuri.v.khan@gmail.com>; arthur.miller@live.com <arthur.miller@live.com>; emacs-devel@gnu.org <emacs-devel@gnu.org>
Ämne: RE: [External] : Re: Is this a bug in while-let or do I missunderstand it?

IMHO, this is a problem with all of the
if/and/when/while-let[*] thingies.  If someone
uses them a lot then she probably knows what
goes on, in what sequence.  But a priori it's
not so clear.

This may just mean that the doc needs to take
pains to be very clear, maybe even with examples
or by showing a macro expansion explicitly.

Using catch/throw, let[*], and/if/when/while
together is always _clearer_, IMO.  And it's
often no more verbose.  Witness all of the
discussion about <X>-let* versus <X>-let names
and this current discussion.  The name itself
doesn't clearly tell you what it does... which
is OK, but only if the doc tells you that
clearly.

I'm not saying no one should use, or Elisp
shouldn't provide, if/and/when/while-let[*]
thingies.  I'm just saying (1) I don't find
them helpful, personally (I don't use them),
and (more importantly) (2) if we provide them
then their doc needs to be very specific about
what _exactly_ they do, and when (if not also
how).

[-- Attachment #2: Type: text/html, Size: 11262 bytes --]

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

* Re: [External] : Re: Is this a bug in while-let or do I missunderstand it?
  2024-11-09 18:18                     ` Alfred M. Szmidt
@ 2024-11-09 20:02                       ` Jens Schmidt
  2024-11-09 20:38                         ` Alfred M. Szmidt
  2024-11-10 11:44                         ` Alfred M. Szmidt
  0 siblings, 2 replies; 59+ messages in thread
From: Jens Schmidt @ 2024-11-09 20:02 UTC (permalink / raw)
  To: Alfred M. Szmidt
  Cc: eliz, yuri.v.khan, arthur.miller, emacs-devel, Drew Adams

> If the bindings are to be reevaluated on each iteration, shouldn't B
> always be NIL, and that it would end up with (< NIL end) would be on
> each iteration causing an error?

I hope I don't miss anything important here, but I think the
first two elements in SPEC of below `while-let'

  (while-let ((b)
              ((< b end))
              (e (next-single-property-change (1+ b) 'erc--msg nil end)))
    ...)

do not actually bind anything, they only test.  The doc string of
`if-let' has:

  Each element of SPEC is a list (SYMBOL VALUEFORM) that binds
  SYMBOL to the value of VALUEFORM.  An element can additionally be
                                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  of the form (VALUEFORM), which is evaluated and checked for nil;
  ^^^^^^^^^^^^^^^^^^^^^^^
  i.e. SYMBOL can be omitted if only the test result is of
  interest.  It can also be of the form SYMBOL, then the binding of
  SYMBOL is checked for nil.

I'd align such clauses like this:

  (while-let ((  b)
              (  (< b end))
              (e (next-single-property-change (1+ b) 'erc--msg nil end)))
    ...)

to emphasize that.



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

* Sv: Is this a bug in while-let or do I missunderstand it?
  2024-11-09 16:33               ` Alfred M. Szmidt
  2024-11-09 16:44                 ` Eli Zaretskii
@ 2024-11-09 20:29                 ` arthur miller
  2024-11-10  6:22                   ` Eli Zaretskii
  1 sibling, 1 reply; 59+ messages in thread
From: arthur miller @ 2024-11-09 20:29 UTC (permalink / raw)
  To: Alfred M. Szmidt, Yuri Khan; +Cc: emacs-devel@gnu.org

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

>> From: "Alfred M. Szmidt" <ams@gnu.org>
>> Cc: arthur.miller@live.com, emacs-devel@gnu.org
>> Date: Sat, 09 Nov 2024 11:33:45 -0500
>>
>>        (while-let ((run (some-condition)))
>>         (message "running"))
>>
>>    Do you expect that to evaluate (some-condition) once, then, if it’s
>>    initially true, run forever?
>>
>> That is how it is described in the manual, so yes (some-condition)
>> should only be done once, and not every iteration.  See (elisp)
>> Conditionals .
>
>Which could mean that the manual is wrong and needs to be fixed.
>The above description actually supports what Yuri was saying, not what
>Arthur and you expect.

Mnjah; if you consider this scatchy C:

{
  int foo = ..;
   ...
  for (int i=0, j=0; u < 10i++ )
  {
        do something with i, j
        .....
        do something with foo
  }

  i,j are not visible here
  ...
}

In other words, there might be variables live outisde of
the loop-scope we wish to access in the loop, and that is
what Yuri's example shows. However, i,j are not re-initiated
on each iteration, but remembers their value. The effecto of
while-let in current implementation is that i,j are re-initiated
in each iteration, not re-evaluated, if that makes it clear.

I am not sure how to illustrate in a better way. The net effect is
that lexical variables declared in while-let loop are "read-only".

They are not, but since they are re-iniated, it is pointless to
write to them.

Of course, all loop predicates should be evaled on each iteration,
but not re-iniated on each iteration. If that makes sense. Sorry,
I am not very good at writing.

When I see at my own KISS version, I see also it only initiates
variable, but does not re-evaluate function calls on each iteration;
I didn't really udnerstand it from the beginning, so this discussion
has cleared my mind a bit too.

However I am not sure exact how to fix it. But I believe a loop
where we can't update loop invariantes is a bit strange too.
________________________________
Från: Alfred M. Szmidt <ams@gnu.org>
Skickat: den 9 november 2024 17:33
Till: Yuri Khan <yuri.v.khan@gmail.com>
Kopia: arthur.miller@live.com <arthur.miller@live.com>; emacs-devel@gnu.org <emacs-devel@gnu.org>
Ämne: Re: Is this a bug in while-let or do I missunderstand it?

   > If it wasn't clear, the unintuitive part is that while-let was to
   > establish the local environment, so that we don't need to type:
   >
   > (let ((som-var (init-form)))
   >     (while some-var
   >         ... ))

   But if it did it that way, the condition (init-form) would only be
   evaluated once, and I’d find *that* counterintuitive. Consider the
   usual form of a while loop:

       (while-let ((run (some-condition)))
         (message "running"))

   Do you expect that to evaluate (some-condition) once, then, if it’s
   initially true, run forever?

That is how it is described in the manual, so yes (some-condition)
should only be done once, and not every iteration.  See (elisp)
Conditionals .

      It can be convenient to bind variables in conjunction with using a
   conditional.  It's often the case that you compute a value, and then
   want to do something with that value if it's non-‘nil’.  The
   straightforward way to do that is to just write, for instance:

        (let ((result1 (do-computation)))
          (when result1
            (let ((result2 (do-more result1)))
              (when result2
                (do-something result2)))))

      Since this is a very common pattern, Emacs provides a number of
   macros to make this easier and more readable.  The above can be written
   the following way instead:

... following the various with various FOO-let forms, ending with
while-let.

[-- Attachment #2: Type: text/html, Size: 11694 bytes --]

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

* Re: [External] : Re: Is this a bug in while-let or do I missunderstand it?
  2024-11-09 20:02                       ` Jens Schmidt
@ 2024-11-09 20:38                         ` Alfred M. Szmidt
  2024-11-09 21:18                           ` Joost Kremers
  2024-11-10 11:44                         ` Alfred M. Szmidt
  1 sibling, 1 reply; 59+ messages in thread
From: Alfred M. Szmidt @ 2024-11-09 20:38 UTC (permalink / raw)
  To: Jens Schmidt; +Cc: eliz, yuri.v.khan, arthur.miller, emacs-devel, drew.adams

   > If the bindings are to be reevaluated on each iteration, shouldn't B
   > always be NIL, and that it would end up with (< NIL end) would be on
   > each iteration causing an error?

   I hope I don't miss anything important here, but I think the
   first two elements in SPEC of below `while-let'

     (while-let ((b)
		 ((< b end))
		 (e (next-single-property-change (1+ b) 'erc--msg nil end)))
       ...)

   do not actually bind anything, they only test.  The doc string of
   `if-let' has:

if-let is not while-let.  If while-let is supposed to do something
weird like that .. then it should mention it.  The examples in the
manual contradict the behaviour, so does the text in the manual.

From the looks, these macros try to be way to smart.

     Each element of SPEC is a list (SYMBOL VALUEFORM) that binds
     SYMBOL to the value of VALUEFORM.  An element can additionally be
					^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
     of the form (VALUEFORM), which is evaluated and checked for nil;
     ^^^^^^^^^^^^^^^^^^^^^^^
     i.e. SYMBOL can be omitted if only the test result is of
     interest.  It can also be of the form SYMBOL, then the binding of
     SYMBOL is checked for nil.

   I'd align such clauses like this:

     (while-let ((  b)
		 (  (< b end))
		 (e (next-single-property-change (1+ b) 'erc--msg nil end)))
       ...)

   to emphasize that.





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

* Re: [External] : Re: Is this a bug in while-let or do I missunderstand it?
  2024-11-09 20:38                         ` Alfred M. Szmidt
@ 2024-11-09 21:18                           ` Joost Kremers
  0 siblings, 0 replies; 59+ messages in thread
From: Joost Kremers @ 2024-11-09 21:18 UTC (permalink / raw)
  To: Alfred M. Szmidt; +Cc: Jens Schmidt, emacs-devel

On Sat, Nov 09 2024, Alfred M. Szmidt wrote:
> From the looks, these macros try to be way to smart.

I wouldn't say that. I've found the behaviour that you talk about to be
quite useful at times. I've run into cases where I want to test a number of
expressions for nil/non-nil, bind some but not all of them, and do
something if they are all non-nil.

-- 
Joost Kremers
Life has its moments



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

* Re: Sv: Is this a bug in while-let or do I missunderstand it?
  2024-11-09 13:47           ` Sv: " arthur miller
  2024-11-09 14:04             ` Yuri Khan
@ 2024-11-09 21:47             ` Joost Kremers
  2024-11-09 22:07               ` Sv: " arthur miller
  2024-11-10  6:07               ` Andreas Schwab
  1 sibling, 2 replies; 59+ messages in thread
From: Joost Kremers @ 2024-11-09 21:47 UTC (permalink / raw)
  To: arthur miller; +Cc: Yuri Khan, emacs-devel@gnu.org

On Sat, Nov 09 2024, arthur miller wrote:
> If it wasn't clear, the unintuitive part is that while-let was to
> establish the local environment, so that we don't need to type:
>
> (let ((som-var (init-form)))
>     (while some-var
>         ... ))
>
> At least is how I understand the purpose of if-let, when-let and while-let.

Yes, but for `while`, the pattern isn't complete. The `setq` inside the
loop is a crucial part:

```
(let ((a (foo ...)))
  (while a
    (do-stuff-with a)
    (setq a (foo ...))))
```

So the idea is that you want to test the result of some expression on each
iteration. `while-let` basically lets you type that expression only once,
instead of twice.

At least that's how I understand `while-let`.


-- 
Joost Kremers
Life has its moments



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

* Sv: Sv: Is this a bug in while-let or do I missunderstand it?
  2024-11-09 21:47             ` Sv: " Joost Kremers
@ 2024-11-09 22:07               ` arthur miller
  2024-11-10  6:07               ` Andreas Schwab
  1 sibling, 0 replies; 59+ messages in thread
From: arthur miller @ 2024-11-09 22:07 UTC (permalink / raw)
  To: Joost Kremers; +Cc: Yuri Khan, emacs-devel@gnu.org

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

>Yes, but for `while`, the pattern isn't complete. The `setq` inside the
>loop is a crucial part:
>
>```
>(let ((a (foo ...)))
>  (while a
>    (do-stuff-with a)
>    (setq a (foo ...))))
>```
>
>So the idea is that you want to test the result of some expression on each
>iteration. `while-let` basically lets you type that expression only once,
>instead of twice.
>
>At least that's how I understand `while-let`.

I understand; I think that was what Yuri also says. However, in that case,
how would one differ setup or initialization from evaluation in each iteration?

If all bindings are cleared and re-evaluated, than the user can not change
the loop invariants from within the loop without setting them elsewhere
outside the loop? I think it is also in a way setting them twice, and they are
no longer lexically enclosed.

The naive-version does ask the user to type that "last setq" manually.
________________________________
Från: Joost Kremers <joostkremers@fastmail.fm>
Skickat: den 9 november 2024 22:47
Till: arthur miller <arthur.miller@live.com>
Kopia: Yuri Khan <yuri.v.khan@gmail.com>; emacs-devel@gnu.org <emacs-devel@gnu.org>
Ämne: Re: Sv: Is this a bug in while-let or do I missunderstand it?

On Sat, Nov 09 2024, arthur miller wrote:
> If it wasn't clear, the unintuitive part is that while-let was to
> establish the local environment, so that we don't need to type:
>
> (let ((som-var (init-form)))
>     (while some-var
>         ... ))
>
> At least is how I understand the purpose of if-let, when-let and while-let.

Yes, but for `while`, the pattern isn't complete. The `setq` inside the
loop is a crucial part:

```
(let ((a (foo ...)))
  (while a
    (do-stuff-with a)
    (setq a (foo ...))))
```

So the idea is that you want to test the result of some expression on each
iteration. `while-let` basically lets you type that expression only once,
instead of twice.

At least that's how I understand `while-let`.


--
Joost Kremers
Life has its moments

[-- Attachment #2: Type: text/html, Size: 5715 bytes --]

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

* RE: [External] : Re: Is this a bug in while-let or do I missunderstand it?
  2024-11-09 19:32                     ` Sv: [External] : Re: Is this a bug in while-let or do I missunderstand it? arthur miller
@ 2024-11-09 22:36                       ` Drew Adams
  2024-11-09 22:53                         ` Drew Adams
  0 siblings, 1 reply; 59+ messages in thread
From: Drew Adams @ 2024-11-09 22:36 UTC (permalink / raw)
  To: arthur miller, Eli Zaretskii, Alfred M. Szmidt
  Cc: yuri.v.khan@gmail.com, emacs-devel@gnu.org

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

Below.

From: arthur miller Sent: Saturday, November 9, 2024 11:33 AM
>IMHO, this is a problem with all of the

>if/and/when/while-let[*] thingies. If someone
>uses them a lot then she probably knows what
>goes on, in what sequence.  But a priori it's
>not so clear.

Only while-let.

The lack of obvious behavior/meaning and clear doc is a problem with them in general, IMO.

In particular, it wouldn't hurt to show an example of what each can expand to, in terms of let[*] etc. We do that in the Elisp manual when introducing cond, for example:

For example: (if A B C)  ≡ (cond (A B) (t C))

(We could even usefully do it to show let* in terms of let.)

>This may just mean that the doc needs to take pains to be very clear, maybe even with examples or by showing a macro expansion explicitly.
>
>Using catch/throw, let[*], and/if/when/while together is always _clearer_, IMO.  And it's often no more verbose.

I don't think so.

(catch/throw aren't needed for some of the *-let constructs, of course.)

IMO, use of explicit let, for binding, and explicit if/when/etc. for control flow, is clear. And if you don't use the *-let thingies much, or are new to them, then the former are clearer. Hence the need for good doc for the *-let combination bind&control constructs, at a minimum.

As you say, the *-let constructs are just "shorthand". And not much shorter, typically. Shorthand, but mentally more complex. The complexity isn't visual, it's in their meanings/behaviors, i.e., mental.

I'm not against all combinations of binding constructs with control constructs. E.g., constructs such as dolist bind vars. I just don't see much mileage/clarity gain from the *-let thingies. YMMV. At a minimum, their doc should be made very clear.

(let ((var1 init1) ... (varN initN)
      (loop-invarant init-loop-invariant))
  ...
  (while loop-invariant ...)
 ...)

In this context you are leaking loop-invariant in the entire scope of enclosing let-form, whereas

(let ((var1 init1) ... (varN initN))
  ...
  (while ((loop-invariant init-loop-invariant))
    ...)
 ...)

limits 'loop-invariant' to the lexical environment of while loop.

What's your argument? That there's no need to bind a local var for init-loop-invariant? OK. How's that relevant here?

(And there's no *-let construct in either of those examples, so ... what are you really comparing/demonstrating?)

In any case, if you did need a local var and wanted to keep its scope within the while you'd just wrap the while with its own let - end of story:

(let ((var1 init1) ... (varN initN)
  ...
  (let ((loop-invarant init-loop-invariant)) (while loop-invariant ...))
 ...)

Using a separate let makes clear where you want/need a separate binding scope.
___

It's enough for someone to scan this mail thread, to see possible confusion over what while-let does and how/when/where it does what --> QED.

E.g., suggestions such as this, to help clarify the meaning/behavior of a while-let, make clear that it isn't so clear on its own:


>I'd align such clauses like this:

>

> (while-let ((  b)

>             (  (< b end))

>             (e (next-single-property-change

>                  (1+ b) 'erc--msg nil end)))

>   ...)

>

>to emphasize that.

I'm not saying such intentional formatting wouldn't help; it could. But suggesting such formatting just underlines how unclear the while-let construct seems to be, a priori.

Again, maybe a doc improvement could help. Or a pointer to this thread, where the back-&-forth might unconfuse someone a bit... ;-)

Från: Drew Adams Skickat: den 9 november 2024 19:07

IMHO, this is a problem with all of the
if/and/when/while-let[*] thingies.  If someone
uses them a lot then she probably knows what
goes on, in what sequence.  But a priori it's
not so clear.

This may just mean that the doc needs to take
pains to be very clear, maybe even with examples
or by showing a macro expansion explicitly.

Using catch/throw, let[*], and/if/when/while
together is always _clearer_, IMO.  And it's
often no more verbose.  Witness all of the
discussion about <X>-let* versus <X>-let names
and this current discussion.  The name itself
doesn't clearly tell you what it does... which
is OK, but only if the doc tells you that
clearly.

I'm not saying no one should use, or Elisp
shouldn't provide, if/and/when/while-let[*]
thingies.  I'm just saying (1) I don't find
them helpful, personally (I don't use them),
and (more importantly) (2) if we provide them
then their doc needs to be very specific about
what _exactly_ they do, and when (if not also
how).

[-- Attachment #2: Type: text/html, Size: 19775 bytes --]

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

* RE: [External] : Re: Is this a bug in while-let or do I missunderstand it?
  2024-11-09 22:36                       ` Drew Adams
@ 2024-11-09 22:53                         ` Drew Adams
  0 siblings, 0 replies; 59+ messages in thread
From: Drew Adams @ 2024-11-09 22:53 UTC (permalink / raw)
  To: Drew Adams, arthur miller, Eli Zaretskii, Alfred M. Szmidt
  Cc: yuri.v.khan@gmail.com, emacs-devel@gnu.org

I said:

> In particular, it wouldn't hurt to show an
> example of what each can expand to, in terms
> of let[*] etc. We do that in the Elisp manual
> when introducing cond, for example:
>
>   For example: (if A B C)  ≡ (cond (A B) (t C))
>
> (We could even usefully do it to show let* in terms of let.)

Took me a moment to find it, but I asked this in
the thread of bug #73853, trying to point out that
it's not obvious what the behavior/meaning is:

> > (if-let ((a (does-an-a-exist?-then-return-it)))
> >     (use a)
> >   (do-something-else))
> 
> Is that the same as this?
> 
> (let ((a (does-an-a-exist?-then-return-it)))
>   (if a (use a) (do-something-else)))
> 
> > (and-let* ((a (an-a-exists))
> >            (b (b-depending-on-a-also-exists)))
> >   (test-using a b))
> 
> Is that the same as this?
> 
> (let* ((a (an-a-exists))
>        (b (b-depending-on-a-also-exists)))
>   (and a b (test-using a b)))
> 
> or this?
> 
> (let* ((a (an-a-exists))
>        (b (and a (b-depending-on-a-also-exists))))
>   (and b (test-using a b)))
> 
> or something else?

I really don't know, and haven't really tried to
find out (unfair, I know), other than by asking
the question there (to which there was no reply).

https://debbugs.gnu.org/cgi/bugreport.cgi?bug=73853#109

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

* Re: Is this a bug in while-let or do I missunderstand it?
  2024-11-09 21:47             ` Sv: " Joost Kremers
  2024-11-09 22:07               ` Sv: " arthur miller
@ 2024-11-10  6:07               ` Andreas Schwab
  1 sibling, 0 replies; 59+ messages in thread
From: Andreas Schwab @ 2024-11-10  6:07 UTC (permalink / raw)
  To: Joost Kremers; +Cc: arthur miller, Yuri Khan, emacs-devel@gnu.org

On Nov 09 2024, Joost Kremers wrote:

> Yes, but for `while`, the pattern isn't complete. The `setq` inside the
> loop is a crucial part:
>
> ```
> (let ((a (foo ...)))
>   (while a
>     (do-stuff-with a)
>     (setq a (foo ...))))
> ```

You can write it like this to avoid typing the condition twice:

 (let (a)
   (while (setq a (foo ...))
     (do-stuff-with a)))

-- 
Andreas Schwab, schwab@linux-m68k.org
GPG Key fingerprint = 7578 EB47 D4E5 4D69 2510  2552 DF73 E780 A9DA AEC1
"And now for something completely different."



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

* Re: Is this a bug in while-let or do I missunderstand it?
  2024-11-09 20:29                 ` Sv: " arthur miller
@ 2024-11-10  6:22                   ` Eli Zaretskii
  2024-11-10 10:40                     ` Joost Kremers
                                       ` (2 more replies)
  0 siblings, 3 replies; 59+ messages in thread
From: Eli Zaretskii @ 2024-11-10  6:22 UTC (permalink / raw)
  To: arthur miller; +Cc: ams, yuri.v.khan, emacs-devel

> From: arthur miller <arthur.miller@live.com>
> CC: "emacs-devel@gnu.org" <emacs-devel@gnu.org>
> Date: Sat, 9 Nov 2024 20:29:29 +0000
> 
> In other words, there might be variables live outisde of
> the loop-scope we wish to access in the loop, and that is
> what Yuri's example shows. However, i,j are not re-initiated
> on each iteration, but remembers their value. The effecto of
> while-let in current implementation is that i,j are re-initiated
> in each iteration, not re-evaluated, if that makes it clear.
> 
> I am not sure how to illustrate in a better way. The net effect is
> that lexical variables declared in while-let loop are "read-only".
> 
> They are not, but since they are re-iniated, it is pointless to
> write to them.

You can write to them indirectly, if the evaluation is properly
written.  If the evaluation is just assigning a fixed value to a
variable, then yes, writing to that variable in the body is pointless;
but then so is the use of while-let, IMO.

Even in your for-loop example from C, the CONDITION part of the loop
is re-evaluated on each iteration, and if you assign some fixed value
to the loop control variables there, your loop might become an
infloop, regardless of what you do in the body with those variables.
That's basically what the example of while-let you show at the
beginning of this discussion did.

> Of course, all loop predicates should be evaled on each iteration,
> but not re-iniated on each iteration. If that makes sense. Sorry,
> I am not very good at writing.

If while-let doesn't seem to do the job in some code of yours, then
don't use it there.  Use something else.  AFAIU, while-let was
introduced for those cases where its use makes sense and does the job
cleaner and clearer than the alternatives.  It could be abused, of
course, but that's not necessarily its fault, is it?

Anyway, to get this long discussion back on track: is there a need to
clarify something in the documentation of while-let? if so, what?



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

* Re: Is this a bug in while-let or do I missunderstand it?
  2024-11-10  6:22                   ` Eli Zaretskii
@ 2024-11-10 10:40                     ` Joost Kremers
  2024-11-10 12:10                       ` Alfred M. Szmidt
  2024-11-10 18:18                     ` arthur miller
  2024-11-11 22:41                     ` Joost Kremers
  2 siblings, 1 reply; 59+ messages in thread
From: Joost Kremers @ 2024-11-10 10:40 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: arthur miller, ams, yuri.v.khan, emacs-devel

On Sun, Nov 10 2024, Eli Zaretskii wrote:
> Anyway, to get this long discussion back on track: is there a need to
> clarify something in the documentation of while-let? if so, what?

Speaking for me personally, I do think the documentation esp. of
`while-let` is too terse. I think two improvements could be made. The first
would be to explicitly mention the pattern that `while-let` replaces, i.e.,

```
(let ((result (do-computation)))
  (while result
    (do-stuff-with result)
    (setq result (do-computation))))
```

The manual at (info "(elisp) Conditionals") discusses the pattern that
`when-let` replaces; `if-let` can be deduced from that, as it's similar.
But `while-let` is different because of the additional `setq` in the
pattern it replaces.

Second, I think it would help if the fact that the bindings are
reestablished upon every iteration were mentioned explicitly. This seems to
have confused Arthur, and I asked myself the same question when I first
encountered `while-let`.

I'd offer the following as a first attempt:

```
@defmac while-let spec then-forms...
Like @code{when-let*}, but repeat until a binding in @var{spec} is
@code{nil}.  The return value is always @code{nil}.

@code{while-let} replaces a common pattern in which a binding is
established outside the @{while}-loop, tested as part of the condition of
@{while} and subsequently changed inside the loop using the same expression
that it was originally bound to:

@example
(let ((result (do-computation)))
  (while result
    (do-stuff-with result)
    (setq result (do-computation))))
@end example

Using @code{while-let}, this can be written more succinctly as:

@example
(while-let ((result (do-computation)))
  (do-stuff-with result))
@end example

The binding of @code{result} is reestablished at every iteration, therefore
setting the value of @code{result} inside the loop has no effect. In order
to end the loop, @code{(do-computation)} should eventually return
@code{nil}.

This example uses a single binding for clarity, but obviously
@code{while-let} can establish multiple bindings. The loop runs as long as
all bindings are non-@code{nil}.
@end defmac
```

Am I mistaken or is `while-let` a bit like a do..until loop that some
languages offer?


-- 
Joost Kremers
Life has its moments



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

* Re: [External] : Re: Is this a bug in while-let or do I missunderstand it?
  2024-11-09 20:02                       ` Jens Schmidt
  2024-11-09 20:38                         ` Alfred M. Szmidt
@ 2024-11-10 11:44                         ` Alfred M. Szmidt
  2024-11-10 12:24                           ` Better documentation for non-binding clauses of if-let and friends Jens Schmidt
  1 sibling, 1 reply; 59+ messages in thread
From: Alfred M. Szmidt @ 2024-11-10 11:44 UTC (permalink / raw)
  To: Jens Schmidt; +Cc: eliz, yuri.v.khan, arthur.miller, emacs-devel, drew.adams


   > If the bindings are to be reevaluated on each iteration, shouldn't B
   > always be NIL, and that it would end up with (< NIL end) would be on
   > each iteration causing an error?

   I hope I don't miss anything important here, but I think the
   first two elements in SPEC of below `while-let'

     (while-let ((b)
		 ((< b end))
		 (e (next-single-property-change (1+ b) 'erc--msg nil end)))
       ...)

   do not actually bind anything, they only test.  The doc string of
   `if-let' has:

     Each element of SPEC is a list (SYMBOL VALUEFORM) that binds
     SYMBOL to the value of VALUEFORM.  An element can additionally be
					^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
     of the form (VALUEFORM), which is evaluated and checked for nil;
     ^^^^^^^^^^^^^^^^^^^^^^^

There is no mention of this in the manual, that only says that SPEC is
like the one in LET*.

The followiung,

(if-let* ((b)
	  ((< 1 2)))
    'foo)

Throws an error that B is not defined.  Clearly this is not at all
like LET*.  So does:

(when-let ((b)
	  ((< 1 2)))
    'foo)

All these are described as "like if-let*".

If this is useful or not is one thing, but clearly these macros try to
be too "smart" and don't follow their own description of what they do
in either docstring or manual.

     i.e. SYMBOL can be omitted if only the test result is of
     interest.  It can also be of the form SYMBOL, then the binding of
     SYMBOL is checked for nil.

   I'd align such clauses like this:

     (while-let ((  b)
		 (  (< b end))
		 (e (next-single-property-change (1+ b) 'erc--msg nil end)))
       ...)

   to emphasize that.





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

* Re: Is this a bug in while-let or do I missunderstand it?
  2024-11-10 10:40                     ` Joost Kremers
@ 2024-11-10 12:10                       ` Alfred M. Szmidt
  2024-11-10 19:49                         ` Sv: " arthur miller
  0 siblings, 1 reply; 59+ messages in thread
From: Alfred M. Szmidt @ 2024-11-10 12:10 UTC (permalink / raw)
  To: Joost Kremers; +Cc: eliz, arthur.miller, yuri.v.khan, emacs-devel

   @defmac while-let spec then-forms...
   Like @code{when-let*}, but repeat until a binding in @var{spec} is
   @code{nil}.  The return value is always @code{nil}.

The "Like FOO" is confusing -- it is not like when-let*, when-let* is
also not like let*.  E.g., when spec is (binding value) or just
(value) (!?) -- which should be mentioned in the manual.

These foo-LET are are mixing up the condition being tested and the
binding, when there is no binding the form seems to be just a test as
if you'd pass it directly to WHEN (or whatever).  There should be some
example that SPEC is not at all like in LET, and that:

(when-let* ((result1 (do-computation))
            (        (do-more result1)))
  (do-something result1))

is something like (I guess?):

(let ((result1 (do-computation)))
  (when result1
    (when (do-more result1)
      (do-something result2))))

And these mentions of "Like LET*" should be removed entierly.

But this is a better, and a good start.

   @code{while-let} replaces a common pattern in which a binding is
   established outside the @{while}-loop, tested as part of the condition of
   @{while} and subsequently changed inside the loop using the same expression
   that it was originally bound to:

   @example
   (let ((result (do-computation)))
     (while result
       (do-stuff-with result)
       (setq result (do-computation))))
   @end example

   Using @code{while-let}, this can be written more succinctly as:

   @example
   (while-let ((result (do-computation)))
     (do-stuff-with result))
   @end example

   The binding of @code{result} is reestablished at every iteration, therefore
   setting the value of @code{result} inside the loop has no effect. In order
   to end the loop, @code{(do-computation)} should eventually return
   @code{nil}.

   This example uses a single binding for clarity, but obviously
   @code{while-let} can establish multiple bindings. The loop runs as long as
   all bindings are non-@code{nil}.
   @end defmac
   ```

   Am I mistaken or is `while-let` a bit like a do..until loop that some
   languages offer?


   -- 
   Joost Kremers
   Life has its moments





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

* Re: Better documentation for non-binding clauses of if-let and friends
  2024-11-10 11:44                         ` Alfred M. Szmidt
@ 2024-11-10 12:24                           ` Jens Schmidt
  2024-11-10 14:51                             ` Sean Whitton
  2024-11-11  8:20                             ` Alfred M. Szmidt
  0 siblings, 2 replies; 59+ messages in thread
From: Jens Schmidt @ 2024-11-10 12:24 UTC (permalink / raw)
  To: Alfred M. Szmidt; +Cc: emacs-devel, Eli Zaretskii

This turned out to be a discussion orthogonal to the one
on while-let, so I think it deserves its own thread ...

On 2024-11-10  12:44, Alfred M. Szmidt wrote:

> If this is useful or not is one thing, but clearly these macros try to
> be too "smart" and don't follow their own description of what they do
> in either docstring or manual.

I rather agree with Joost on that other thread regarding the
usefulness of the FOO-let macros and their condition-only,
non-binding clauses.

> There is no mention of this in the manual, that only says that SPEC is
> like the one in LET*.

But I agree with you that the manual is incomplete or even
wrong here.

How about the following in section "Conditionals" instead
of what there currently is about these:

  There's a number of variations on this theme, and they're briefly
  described below.

  For all of these SPEC is similar to what let* offers, with a few
  extensions useful in the context of testing conditions: As with
  let*, an element of SPEC which is a list (SYMBOL VALUEFORM) binds
  SYMBOL to the value of VALUEFORM.  An element can additionally be
  of the form (VALUEFORM), which is evaluated and checked for nil;
  i.e. SYMBOL can be omitted if only the test result is of
  interest.  It can also be of the form SYMBOL, then the binding of
  SYMBOL is checked for nil.

   -- Macro: if-let spec then-form else-forms...
       Evaluate each binding in SPEC in turn, stopping if a binding
       value or value form is ‘nil’.  If all are non-‘nil’, return
       the value of THEN-FORM, otherwise the last form in
       ELSE-FORMS.

   -- ...

If needed, I can provide that or something similar as a patch ...




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

* Re: Better documentation for non-binding clauses of if-let and friends
  2024-11-10 12:24                           ` Better documentation for non-binding clauses of if-let and friends Jens Schmidt
@ 2024-11-10 14:51                             ` Sean Whitton
  2024-11-10 16:58                               ` Jens Schmidt
  2024-11-11 10:03                               ` Alfred M. Szmidt
  2024-11-11  8:20                             ` Alfred M. Szmidt
  1 sibling, 2 replies; 59+ messages in thread
From: Sean Whitton @ 2024-11-10 14:51 UTC (permalink / raw)
  To: Jens Schmidt, Joost Kremers; +Cc: Alfred M. Szmidt, emacs-devel, Eli Zaretskii

Hello,

On Sun 10 Nov 2024 at 01:24pm +01, Jens Schmidt wrote:

>   For all of these SPEC is similar to what let* offers, with a few
>   extensions useful in the context of testing conditions: As with
>   let*, an element of SPEC which is a list (SYMBOL VALUEFORM) binds
>   SYMBOL to the value of VALUEFORM.

I think we should make sure we keep the reference to let*, indeed.

We seem to have two proposed patches, now, from Joost and Jens.

Would one of you kindly combine them, and also post it as an actual
diff?

-- 
Sean Whitton



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

* Re: Better documentation for non-binding clauses of if-let and friends
  2024-11-10 14:51                             ` Sean Whitton
@ 2024-11-10 16:58                               ` Jens Schmidt
  2024-11-11 10:03                               ` Alfred M. Szmidt
  1 sibling, 0 replies; 59+ messages in thread
From: Jens Schmidt @ 2024-11-10 16:58 UTC (permalink / raw)
  To: Joost Kremers; +Cc: Alfred M. Szmidt, emacs-devel, Eli Zaretskii, Sean Whitton

Joost,

who takes the lock?  Merging should be pretty simple, I think.
One could also add an example for a non-binding clause, like in

  (when-let* ((end (calculate-end))
              (    (< point end)))
    (do-something-with end))

Jens

On 2024-11-10  15:51, Sean Whitton wrote:
> Hello,
> 
> On Sun 10 Nov 2024 at 01:24pm +01, Jens Schmidt wrote:
> 
>>   For all of these SPEC is similar to what let* offers, with a few
>>   extensions useful in the context of testing conditions: As with
>>   let*, an element of SPEC which is a list (SYMBOL VALUEFORM) binds
>>   SYMBOL to the value of VALUEFORM.
> 
> I think we should make sure we keep the reference to let*, indeed.
> 
> We seem to have two proposed patches, now, from Joost and Jens.
> 
> Would one of you kindly combine them, and also post it as an actual
> diff?
> 




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

* Sv: Is this a bug in while-let or do I missunderstand it?
  2024-11-10  6:22                   ` Eli Zaretskii
  2024-11-10 10:40                     ` Joost Kremers
@ 2024-11-10 18:18                     ` arthur miller
  2024-11-11  5:13                       ` Yuri Khan
  2024-11-11 22:41                     ` Joost Kremers
  2 siblings, 1 reply; 59+ messages in thread
From: arthur miller @ 2024-11-10 18:18 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: ams@gnu.org, yuri.v.khan@gmail.com, emacs-devel@gnu.org

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

>> From: arthur miller <arthur.miller@live.com>
>> CC: "emacs-devel@gnu.org" <emacs-devel@gnu.org>
>> Date: Sat, 9 Nov 2024 20:29:29 +0000
>>
>> In other words, there might be variables live outisde of
>> the loop-scope we wish to access in the loop, and that is
>> what Yuri's example shows. However, i,j are not re-initiated
>> on each iteration, but remembers their value. The effecto of
>> while-let in current implementation is that i,j are re-initiated
>> in each iteration, not re-evaluated, if that makes it clear.
>>
>> I am not sure how to illustrate in a better way. The net effect is
>> that lexical variables declared in while-let loop are "read-only".
>>
>> They are not, but since they are re-iniated, it is pointless to
>> write to them.
>
>You can write to them indirectly, if the evaluation is properly
>written.  If the evaluation is just assigning a fixed value to a
>variable, then yes, writing to that variable in the body is pointless;
>but then so is the use of while-let, IMO.

That depends how you define the while-let semantics of course.

>Even in your for-loop example from C, the CONDITION part of the loop
>is re-evaluated on each iteration, and if you assign some fixed value
>to the loop control variables there, your loop might become an
>infloop, regardless of what you do in the body with those variables.

Infloops are not in question here, since they are results of a programmers
misstake. The job of for-loop in C or while-let in Emacs is not to ensure
always terminating loop.

>That's basically what the example of while-let you show at the
>beginning of this discussion did.

As I understand it now, Emacs while-loop is a unique kind of loop, at least
I have never seen a construction with such semantic before. The semantic
of while-let in Emacs is that of for-loop or while-loop in C++, but where
initialization of loop variables happens on each iteration

    for (int i=0; i<some_bound(); i++) {
        we can read i here, but
        we can't write to it
    }

Since re-initialization is happening on each iteration, as you say we have
to go via a third variable (or a function), and the variable must not be shadowed
by the local binding established by while-let spec. That does make while-let
pointless to use for not so unusual idiom in other languages:

(let ((run t))
  (while run
     ...
     (when (some-condition)
       (setf run nil))))

I personally would expect to be able to write:

(while-let ((run t))
  ...
  (when (some-condition)
  (setf run nil)))

Since while-let was supposed to simplify that case, but that wont work :).

So obviously I did missunderstood how while-let works, but I would say the
semantics are bit arcane. I haven't seen any other language with read-only
semantic for loop variables.

Somebody said in another mail that bindings are conditions. I think it is a
good illustration of while-let bindings, but it perhaps does not catch the
fact that they are not used as ordinary lexical bindings strongly enough, so,
perhaps a mention about not being settable from the loop is in order?

That would also be my answer for the question in another mail about what
to add to docs. Also perhaps mention that the way out of that loop is to either
go via an implicit variable, or to use try/catch or cl-block/cl-return-from to
break out of the loop.

As a remark:

Perhaps while-let is a wrong name for this construct to start with. It behaves
more like for-loops, but with a twist. Typically in C/C++ and derivatives, we
can't introduce a variable in while condition: while (int i = some_var ) is not
legal.

Since those bindings are not settable in loop body; they really are conditions,
perhaps while* is a better name for this construct, since it introduces
multiple conditions. But it is not good either, since this construct is really
somewhere in-between a while and for loop from other languages.





________________________________
Från: Eli Zaretskii <eliz@gnu.org>
Skickat: den 10 november 2024 07:22
Till: arthur miller <arthur.miller@live.com>
Kopia: ams@gnu.org <ams@gnu.org>; yuri.v.khan@gmail.com <yuri.v.khan@gmail.com>; emacs-devel@gnu.org <emacs-devel@gnu.org>
Ämne: Re: Is this a bug in while-let or do I missunderstand it?

> From: arthur miller <arthur.miller@live.com>
> CC: "emacs-devel@gnu.org" <emacs-devel@gnu.org>
> Date: Sat, 9 Nov 2024 20:29:29 +0000
>
> In other words, there might be variables live outisde of
> the loop-scope we wish to access in the loop, and that is
> what Yuri's example shows. However, i,j are not re-initiated
> on each iteration, but remembers their value. The effecto of
> while-let in current implementation is that i,j are re-initiated
> in each iteration, not re-evaluated, if that makes it clear.
>
> I am not sure how to illustrate in a better way. The net effect is
> that lexical variables declared in while-let loop are "read-only".
>
> They are not, but since they are re-iniated, it is pointless to
> write to them.

You can write to them indirectly, if the evaluation is properly
written.  If the evaluation is just assigning a fixed value to a
variable, then yes, writing to that variable in the body is pointless;
but then so is the use of while-let, IMO.

Even in your for-loop example from C, the CONDITION part of the loop
is re-evaluated on each iteration, and if you assign some fixed value
to the loop control variables there, your loop might become an
infloop, regardless of what you do in the body with those variables.
That's basically what the example of while-let you show at the
beginning of this discussion did.

> Of course, all loop predicates should be evaled on each iteration,
> but not re-iniated on each iteration. If that makes sense. Sorry,
> I am not very good at writing.

If while-let doesn't seem to do the job in some code of yours, then
don't use it there.  Use something else.  AFAIU, while-let was
introduced for those cases where its use makes sense and does the job
cleaner and clearer than the alternatives.  It could be abused, of
course, but that's not necessarily its fault, is it?

Anyway, to get this long discussion back on track: is there a need to
clarify something in the documentation of while-let? if so, what?

[-- Attachment #2: Type: text/html, Size: 18472 bytes --]

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

* Sv: Is this a bug in while-let or do I missunderstand it?
  2024-11-10 12:10                       ` Alfred M. Szmidt
@ 2024-11-10 19:49                         ` arthur miller
  0 siblings, 0 replies; 59+ messages in thread
From: arthur miller @ 2024-11-10 19:49 UTC (permalink / raw)
  To: Alfred M. Szmidt, Joost Kremers
  Cc: eliz@gnu.org, yuri.v.khan@gmail.com, emacs-devel@gnu.org

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

>   @defmac while-let spec then-forms...
>   Like @code{when-let*}, but repeat until a binding in @var{spec} is
>   @code{nil}.  The return value is always @code{nil}.
>
>The "Like FOO" is confusing -- it is not like when-let*, when-let* is
>also not like let*.  E.g., when spec is (binding value) or just
>(value) (!?) -- which should be mentioned in the manual.
>
>These foo-LET are are mixing up the condition being tested and the
>binding, when there is no binding the form seems to be just a test as
>if you'd pass it directly to WHEN (or whatever).  There should be some
>example that SPEC is not at all like in LET, and that:
>
>(when-let* ((result1 (do-computation))
>            (        (do-more result1)))
>  (do-something result1))
>
>is something like (I guess?):
>
>(let ((result1 (do-computation)))
>  (when result1
>    (when (do-more result1)
>      (do-something result2))))
>
>And these mentions of "Like LET*" should be removed entierly.
>
>But this is a better, and a good start.
>
>   @code{while-let} replaces a common pattern in which a binding is
>   established outside the @{while}-loop, tested as part of the condition of
>   @{while} and subsequently changed inside the loop using the same expression
>   that it was originally bound to:
>
>   @example
>   (let ((result (do-computation)))
>     (while result
>       (do-stuff-with result)
>       (setq result (do-computation))))
>   @end example
>
>   Using @code{while-let}, this can be written more succinctly as:
>
>   @example
>   (while-let ((result (do-computation)))
>     (do-stuff-with result))
>   @end example
>
>   The binding of @code{result} is reestablished at every iteration, therefore
>   setting the value of @code{result} inside the loop has no effect. In order
>   to end the loop, @code{(do-computation)} should eventually return
>   @code{nil}.
>
>   This example uses a single binding for clarity, but obviously
>   @code{while-let} can establish multiple bindings. The loop runs as long as
>   all bindings are non-@code{nil}.
>   @end defmac
>   ```
>
>   Am I mistaken or is `while-let` a bit like a do..until loop that some
>   languages offer?

Isn't it also like named-let? But without the ability to call itself recursivey.
I haven't tried it yet, but seems like while-let is a special case of named-let,
an "anonymous named-let" with conditions passed as-they-are. In other words we could
generate while-let as named-let with gensym as the name? (if cl-lib was allowed in
subr.el so to say). I am not sure if I have done it correctly, probably not, but here
is a try:

(defmacro while-test (spec &rest body)
  (declare (indent defun))
  (let* ((name (gensym "while-let-"))
         (bindings (if (and (consp spec) (symbolp (car spec)))
                       (list spec)
                     spec)))
    `(named-let ,name ,spec
       ,@body
       (if (not (and ,@(mapcar #'car bindings)))
           nil
           (,name ,@(mapcar #'cadr bindings))))))
________________________________
Från: Alfred M. Szmidt <ams@gnu.org>
Skickat: den 10 november 2024 13:10
Till: Joost Kremers <joostkremers@fastmail.fm>
Kopia: eliz@gnu.org <eliz@gnu.org>; arthur.miller@live.com <arthur.miller@live.com>; yuri.v.khan@gmail.com <yuri.v.khan@gmail.com>; emacs-devel@gnu.org <emacs-devel@gnu.org>
Ämne: Re: Is this a bug in while-let or do I missunderstand it?

   @defmac while-let spec then-forms...
   Like @code{when-let*}, but repeat until a binding in @var{spec} is
   @code{nil}.  The return value is always @code{nil}.

The "Like FOO" is confusing -- it is not like when-let*, when-let* is
also not like let*.  E.g., when spec is (binding value) or just
(value) (!?) -- which should be mentioned in the manual.

These foo-LET are are mixing up the condition being tested and the
binding, when there is no binding the form seems to be just a test as
if you'd pass it directly to WHEN (or whatever).  There should be some
example that SPEC is not at all like in LET, and that:

(when-let* ((result1 (do-computation))
            (        (do-more result1)))
  (do-something result1))

is something like (I guess?):

(let ((result1 (do-computation)))
  (when result1
    (when (do-more result1)
      (do-something result2))))

And these mentions of "Like LET*" should be removed entierly.

But this is a better, and a good start.

   @code{while-let} replaces a common pattern in which a binding is
   established outside the @{while}-loop, tested as part of the condition of
   @{while} and subsequently changed inside the loop using the same expression
   that it was originally bound to:

   @example
   (let ((result (do-computation)))
     (while result
       (do-stuff-with result)
       (setq result (do-computation))))
   @end example

   Using @code{while-let}, this can be written more succinctly as:

   @example
   (while-let ((result (do-computation)))
     (do-stuff-with result))
   @end example

   The binding of @code{result} is reestablished at every iteration, therefore
   setting the value of @code{result} inside the loop has no effect. In order
   to end the loop, @code{(do-computation)} should eventually return
   @code{nil}.

   This example uses a single binding for clarity, but obviously
   @code{while-let} can establish multiple bindings. The loop runs as long as
   all bindings are non-@code{nil}.
   @end defmac
   ```

   Am I mistaken or is `while-let` a bit like a do..until loop that some
   languages offer?


   --
   Joost Kremers
   Life has its moments



[-- Attachment #2: Type: text/html, Size: 16150 bytes --]

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

* Re: Is this a bug in while-let or do I missunderstand it?
  2024-11-10 18:18                     ` arthur miller
@ 2024-11-11  5:13                       ` Yuri Khan
  2024-11-11  8:49                         ` Sv: " arthur miller
  0 siblings, 1 reply; 59+ messages in thread
From: Yuri Khan @ 2024-11-11  5:13 UTC (permalink / raw)
  To: arthur miller; +Cc: Eli Zaretskii, ams@gnu.org, emacs-devel@gnu.org

On Mon, 11 Nov 2024 at 01:18, arthur miller <arthur.miller@live.com> wrote:

> As I understand it now, Emacs while-loop is a unique kind of loop, at least
> I have never seen a construction with such semantic before. The semantic
> of while-let in Emacs is that of for-loop or while-loop in C++, but where
> initialization of loop variables happens on each iteration
>
>     for (int i=0; i<some_bound(); i++) {
>         we can read i here, but
>         we can't write to it
>     }

Comprarison with a for loop is somewhat strained here. The while-let
loop in Elisp is directly analogous to this C++ while loop:

    #include <iostream>

    int main() {
        while (bool run = true) {
            std::cout << "running\n";
            run = false;
        }
        std::cout << "out of loop\n";
    }

and yes, it’s an infloop, too.

What you’re looking for, though, seems to be a while loop with a
break, which is expressed as a catch/throw in Elisp.



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

* Re: Better documentation for non-binding clauses of if-let and friends
  2024-11-10 12:24                           ` Better documentation for non-binding clauses of if-let and friends Jens Schmidt
  2024-11-10 14:51                             ` Sean Whitton
@ 2024-11-11  8:20                             ` Alfred M. Szmidt
  1 sibling, 0 replies; 59+ messages in thread
From: Alfred M. Szmidt @ 2024-11-11  8:20 UTC (permalink / raw)
  To: Jens Schmidt; +Cc: emacs-devel, eliz

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1: Type: text/plain, Size: 2350 bytes --]

   I rather agree with Joost on that other thread regarding the
   usefulness of the FOO-let macros and their condition-only,
   non-binding clauses.

I don't think anyone is arguing about their usefulness, only that the
macros are too smart for their own good.

   > There is no mention of this in the manual, that only says that SPEC is
   > like the one in LET*.

   But I agree with you that the manual is incomplete or even
   wrong here.

   How about the following in section "Conditionals" instead
   of what there currently is about these:

     There's a number of variations on this theme, and they're briefly
     described below.

     For all of these SPEC is similar to what let* offers, with a few
     extensions useful in the context of testing conditions: As with
     let*, an element of SPEC which is a list (SYMBOL VALUEFORM) binds
     SYMBOL to the value of VALUEFORM.  An element can additionally be
     of the form (VALUEFORM), which is evaluated and checked for nil;
     i.e. SYMBOL can be omitted if only the test result is of
     interest.  It can also be of the form SYMBOL, then the binding of
     SYMBOL is checked for nil.


     For all of these SPEC is similar to what let* offers, with a few
     extensions useful in the context of testing conditions:

That sentence doesn't add much, and will make the user guess if there
are other useful extentions -- "with the following extension" would
suffice I think.

     As with
     let*, an element of SPEC which is a list (SYMBOL VALUEFORM) binds
     SYMBOL to the value of VALUEFORM.  An element can additionally be
     of the form (VALUEFORM), 
     which is evaluated and checked for nil;
     i.e. SYMBOL can be omitted if only the test result is of
     interest.  It can also be of the form SYMBOL, then the binding of
     SYMBOL is checked for nil.


      -- Macro: if-let spec then-form else-forms...
	  Evaluate each binding in SPEC in turn, stopping if a binding
	  value or value form is ‘nil’.  If all are non-‘nil’, return
	  the value of THEN-FORM, otherwise the last form in
	  ELSE-FORMS.

The description here should explicitly mention that SPEC is _NOT_ a
binding if the spec only contains the VALUEFORM.  And so should all
other descriptions.

      -- ...

   If needed, I can provide that or something similar as a patch ...






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

* Sv: Is this a bug in while-let or do I missunderstand it?
  2024-11-11  5:13                       ` Yuri Khan
@ 2024-11-11  8:49                         ` arthur miller
  2024-11-11 12:23                           ` tomas
  0 siblings, 1 reply; 59+ messages in thread
From: arthur miller @ 2024-11-11  8:49 UTC (permalink / raw)
  To: Yuri Khan; +Cc: Eli Zaretskii, ams@gnu.org, emacs-devel@gnu.org

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

>Comprarison with a for loop is somewhat strained here. The while-let

I didn't meant to say that while-let was equivalent to that for-loop;
but tried to illustrate the expectaions. I hope it was clear from the rest.

>loop in Elisp is directly analogous to this C++ while loop:
>
>    #include <iostream>
>
>    int main() {
>        while (bool run = true) {
>            std::cout << "running\n";
>            run = false;
>        }
>        std::cout << "out of loop\n";
>    }
>
>and yes, it’s an infloop, too.

Actually didn't know we can introduce new variable in while declaration in
C++; in C it is verbotten:

~/repos/test $ gcc -o test test.c
test.c: In function 'main':
test.c:3:10: error: expected expression before 'int'
    3 |   while (int i = 0) {
      |          ^~~
test.c:4:5: error: 'i' undeclared (first use in this function)
    4 |     i++;
      |     ^
test.c:4:5: note: each undeclared identifier is reported only once for each function it appears in

But that is just a regression (thought it as in C++ too :-)).

>What you’re looking for, though, seems to be a while loop with a
>break, which is expressed as a catch/throw in Elisp.

Yes, that is what I came to as well, if you check the rest of the
response to Eli as I suggested to mention catch/throw or cl-block/cl-return-from
in the docs.

Even better is named-let, which seems to be a general
version of while-let:

(defmacro while-test (spec &rest body)
  (declare (indent defun))
  (let* ((name (gensym "while-let-"))
         (bindings (if (and (consp spec) (symbolp (car spec)))
                       (list spec)
                     spec)))
    `(named-let ,name ,spec
       ,@body
       (if (not (and ,@(mapcar #'car bindings)))
           nil
         (,name ,@(mapcar #'cadr bindings))))))

(pp (macroexpand-1
     '(while-test ((run t))
        (setf run nil))) (current-buffer))

(named-let while-let-141 ((run t))
  (setf run nil) (if (not (and run)) nil (while-let-141 t)))

(pp (macroexpand-1
     '(while-let ((run t))
        (setf run nil))) (current-buffer))

(catch 'done140
  (while t (if-let* ((run t)) (progn (setf run nil)) (throw 'done140 nil))))

As seen, they both expand to equivalent infinite loop.

For the illustration, named-let expands to a nice while loop itself:

(pp (macroexpand-all
     '(while-test ((run t))
        (setf run nil))) (current-buffer))

(let ((run t))
  (let (retval)
    (while
        (let ((run run))
          (progn
            (setq run nil)
            (if (not (and run)) nil (progn (setq run t) :recurse)))))
    retval))

In my personal opinion while-let, while meant to be a "shortcut" to
certain style of expressions is a bit unfortunate name, since the "-let"
part of the name suggest establishing an environment around the body,
however that environment is read only which is not normal semantic of
let-bindings.  In other words, the devil is in the details which perhaps was
 not intentional?

[-- Attachment #2: Type: text/html, Size: 14124 bytes --]

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

* Re: Better documentation for non-binding clauses of if-let and friends
@ 2024-11-11  9:28 arthur miller
  2024-11-11  9:58 ` Alfred M. Szmidt
  0 siblings, 1 reply; 59+ messages in thread
From: arthur miller @ 2024-11-11  9:28 UTC (permalink / raw)
  To: Alfred M. Szmidt, emacs-devel@gnu.org

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

>   I rather agree with Joost on that other thread regarding the
>   usefulness of the FOO-let macros and their condition-only,
>   non-binding clauses.
>
>I don't think anyone is arguing about their usefulness, only that the
>macros are too smart for their own good.

I think I will start argue against while-let usefulness :).

Now when I have seen that while-let is a special case of named-let,
I think that while-let is a bad construct for some reasons:

1. it is not general
2. the special case in which it does not work is hidden
3. the semantic of "read-only" loop variables is uncommon and unexpected

>   > There is no mention of this in the manual, that only says that SPEC is
>   > like the one in LET*.

That is the gotcha that got me: it says SPEC is "like let*", so this "-let*"
in the name take my mind to believe it established ordinary let*-bindings.
However, in while-let, these are not ordinary, but read-only. So they are
not the same, since they don't obey the ordinary behavior of let* bindings.

>   But I agree with you that the manual is incomplete or even
>   wrong here.

If that semantic of while-let is desirable to have, than the manual would
have to catch the details of while-let and its non-general nature, read-only
semantic of bindings and perhaps mention the named-let as a more general
 alternative.

[-- Attachment #2: Type: text/html, Size: 5172 bytes --]

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

* Re: Better documentation for non-binding clauses of if-let and friends
  2024-11-11  9:28 Better documentation for non-binding clauses of if-let and friends arthur miller
@ 2024-11-11  9:58 ` Alfred M. Szmidt
  2024-11-11 10:26   ` Joost Kremers
  0 siblings, 1 reply; 59+ messages in thread
From: Alfred M. Szmidt @ 2024-11-11  9:58 UTC (permalink / raw)
  To: arthur miller; +Cc: emacs-devel

   That is the gotcha that got me: it says SPEC is "like let*", so this "-let*"
   in the name take my mind to believe it established ordinary let*-bindings.
   However, in while-let, these are not ordinary, but read-only. So they are
   not the same, since they don't obey the ordinary behavior of let* bindings.

I agree, but the question really is what should be done -- either
satisfy one camp or the other.

I personally do not get what one gets from using WHILE-LET -- the
construct seems forced, and very rare to use.

IF-LET, WHEN-LET I can maybe guess but they too seem force constructs,
and why isn't there a UNLESS-LET and OR-LET*? But I'll leave it at
that, personal preferences and all.

   >   But I agree with you that the manual is incomplete or even
   >   wrong here.

   If that semantic of while-let is desirable to have, than the manual would
   have to catch the details of while-let and its non-general nature, read-only
   semantic of bindings and perhaps mention the named-let as a more general
    alternative.

Yup.



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

* Re: Better documentation for non-binding clauses of if-let and friends
  2024-11-10 14:51                             ` Sean Whitton
  2024-11-10 16:58                               ` Jens Schmidt
@ 2024-11-11 10:03                               ` Alfred M. Szmidt
  1 sibling, 0 replies; 59+ messages in thread
From: Alfred M. Szmidt @ 2024-11-11 10:03 UTC (permalink / raw)
  To: Sean Whitton; +Cc: jschmidt4gnu, joostkremers, emacs-devel, eliz

   >   For all of these SPEC is similar to what let* offers, with a few
   >   extensions useful in the context of testing conditions: As with
   >   let*, an element of SPEC which is a list (SYMBOL VALUEFORM) binds
   >   SYMBOL to the value of VALUEFORM.

   I think we should make sure we keep the reference to let*, indeed.

That just causes confusion, LET* bindings can be changed, they do not
support a spec binding that is of the form VALUEOFORM (which in a
LET/LET* binding would be SYMBOL).  They are not similar at all.

Leaving out references to LET and just explaining how the binding spec
actually works would be much better, and not lead to confusion.

   Would one of you kindly combine them, and also post it as an actual
   diff?

   -- 
   Sean Whitton





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

* Re: Better documentation for non-binding clauses of if-let and friends
  2024-11-11  9:58 ` Alfred M. Szmidt
@ 2024-11-11 10:26   ` Joost Kremers
  0 siblings, 0 replies; 59+ messages in thread
From: Joost Kremers @ 2024-11-11 10:26 UTC (permalink / raw)
  To: Alfred M. Szmidt; +Cc: arthur miller, emacs-devel

On Mon, Nov 11 2024, Alfred M. Szmidt wrote:
> IF-LET, WHEN-LET I can maybe guess but they too seem force constructs,

I just recently wrote a simple RDP that has a couple of functions of this
form:

```
(if-let* (((parsebib--char "@"))
          ((parsebib--keyword '("string")))
          (open (parsebib--char "{("))
          (definition (parsebib--assignment))
          ((parsebib--char (alist-get open '((?\{ . "}") (?\( . ")"))))))
    definition
  (signal 'parsebib-error (list (format "Malformed @String definition at position %d,%d"
                                        (line-number-at-pos) (current-column)))))
```

This reads a complete BibTeX @String definition. The clauses in the
`if-let*` specify what text should be read, and I can capture the result of
those reads, but only for the parts that I need to capture.

I think once you know how `if-let*` works, this is a fairly concise and
quite readable way to code this rule. Not sure what the equivalent without
`if-let*` would look like, TBH. (Short of creating a DSL, which would
admittedly be nicer...)


-- 
Joost Kremers
Life has its moments



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

* Re: Sv: Is this a bug in while-let or do I missunderstand it?
  2024-11-11  8:49                         ` Sv: " arthur miller
@ 2024-11-11 12:23                           ` tomas
  0 siblings, 0 replies; 59+ messages in thread
From: tomas @ 2024-11-11 12:23 UTC (permalink / raw)
  To: arthur miller; +Cc: emacs-devel@gnu.org

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

On Mon, Nov 11, 2024 at 08:49:39AM +0000, arthur miller wrote:

[...]

> Actually didn't know we can introduce new variable in while declaration in
> C++; in C it is verbotten:
> 
> ~/repos/test $ gcc -o test test.c
> test.c: In function 'main':
> test.c:3:10: error: expected expression before 'int'
>     3 |   while (int i = 0) {
>       |          ^~~
> test.c:4:5: error: 'i' undeclared (first use in this function)
>     4 |     i++;
>       |     ^
> test.c:4:5: note: each undeclared identifier is reported only once for each function it appears in

AFAIK, it is erlaubt since C99.

Cheers
-- 
t

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 195 bytes --]

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

* Re: Is this a bug in while-let or do I missunderstand it?
  2024-11-10  6:22                   ` Eli Zaretskii
  2024-11-10 10:40                     ` Joost Kremers
  2024-11-10 18:18                     ` arthur miller
@ 2024-11-11 22:41                     ` Joost Kremers
  2024-11-12 12:19                       ` Eli Zaretskii
  2 siblings, 1 reply; 59+ messages in thread
From: Joost Kremers @ 2024-11-11 22:41 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: arthur miller, ams, yuri.v.khan, emacs-devel

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

On Sun, Nov 10 2024, Eli Zaretskii wrote:
> Anyway, to get this long discussion back on track: is there a need to
> clarify something in the documentation of while-let? if so, what?

Patch attached. Hope it's not too long, while still covering the gist of
what was discussed in this thread.

HTH

-- 
Joost Kremers
Life has its moments


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Improve-documentation-for-while-let.patch --]
[-- Type: text/x-patch, Size: 3621 bytes --]

From fb3fd3bef67de821c469c0edb5b1cd6680736565 Mon Sep 17 00:00:00 2001
From: Joost Kremers <joostkremers@fastmail.com>
Date: Mon, 11 Nov 2024 23:38:49 +0100
Subject: [PATCH] Improve documentation for `while-let'.

* doc/lispref/control.texi: Improve documentation for `while-let`.
---
 doc/lispref/control.texi | 51 +++++++++++++++++++++++++++++++++-------
 1 file changed, 43 insertions(+), 8 deletions(-)

diff --git a/doc/lispref/control.texi b/doc/lispref/control.texi
index 80ed2ce899b..efc57fe7323 100644
--- a/doc/lispref/control.texi
+++ b/doc/lispref/control.texi
@@ -321,33 +321,68 @@ Conditionals
 There's a number of variations on this theme, and they're briefly
 described below.
 
-@defmac if-let* varlist then-form else-forms...
-Evaluate each binding in @var{varlist} in turn, like in @code{let*}
+@defmac if-let* spec then-form else-forms...
+Evaluate each binding in @var{spec} in turn, like in @code{let*}
 (@pxref{Local Variables}), stopping if a binding value is @code{nil}.
 If all are non-@code{nil}, return the value of @var{then-form},
 otherwise the last form in @var{else-forms}.
 @end defmac
 
-@defmac when-let* varlist then-forms...
+@defmac when-let* spec then-forms...
 Like @code{if-let*}, but without @var{else-forms}.
 @end defmac
 
-@defmac and-let* varlist then-forms...
+@defmac and-let* spec then-forms...
 Like @code{when-let*}, but in addition, if there are no
 @var{then-forms} and all the bindings evaluate to non-@code{nil}, return
 the value of the last binding.
 @end defmac
 
-@defmac while-let spec then-forms...
-Like @code{when-let*}, but repeat until a binding in @var{spec} is
-@code{nil}.  The return value is always @code{nil}.
-@end defmac
+Each element of the @code{spec} argument in these macros has the form
+@code{(@var{symbol} @var{value-form})}: @var{value-form} is evaluated
+and @var{symbol} is locally bound to the result.  As a special case,
+@var{symbol} can be omitted if the result of @var{value-form} just needs
+to be tested and there's no need to bind it to a variable.
 
 Some Lisp programmers follow the convention that @code{and} and
 @code{and-let*} are for forms evaluated for return value, and
 @code{when} and @code{when-let*} are for forms evaluated for side-effect
 with returned values ignored.
 
+A similar macro exists to run a loop until one binding evaluates to
+@code{nil}:
+
+@defmac while-let spec then-forms...
+Evaluate each binding in @var{spec} in turn, stopping if a binding value
+is @code{nil}.  If all are non-@code{nil}, execute @var{then-forms},
+then repeat the loop.  The return value is always @code{nil}.
+
+@code{while-let} replaces a pattern in which a binding is established
+outside the @code{while}-loop, tested as part of the condition of
+@code{while} and subsequently changed inside the loop using the same
+expression that it was originally bound to:
+
+@example
+(let ((result (do-computation)))
+  (while result
+    (do-stuff-with result)
+    (setq result (do-computation))))
+@end example
+
+Using @code{while-let}, this can be written more succinctly as:
+
+@example
+(while-let ((result (do-computation)))
+  (do-stuff-with result))
+@end example
+
+One crucial difference here is the fact that in the first code example,
+@code{result} is scoped outside the @code{wile} loop, whereas in the
+second example, its scope is confined to the @code{while-let} loop.  As
+a result, changing the value of @code{result} inside the loop has no
+effect on the subsequent iteration.
+@end defmac
+
 @node Combining Conditions
 @section Constructs for Combining Conditions
 @cindex combining conditions
-- 
2.47.0


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

* Re: Is this a bug in while-let or do I missunderstand it?
  2024-11-11 22:41                     ` Joost Kremers
@ 2024-11-12 12:19                       ` Eli Zaretskii
  2024-11-12 12:45                         ` Joost Kremers
  2024-11-12 23:45                         ` Joost Kremers
  0 siblings, 2 replies; 59+ messages in thread
From: Eli Zaretskii @ 2024-11-12 12:19 UTC (permalink / raw)
  To: Joost Kremers; +Cc: arthur.miller, ams, yuri.v.khan, emacs-devel

> From: Joost Kremers <joostkremers@fastmail.fm>
> Cc: arthur miller <arthur.miller@live.com>,  ams@gnu.org,
>   yuri.v.khan@gmail.com,  emacs-devel@gnu.org
> Date: Mon, 11 Nov 2024 23:41:50 +0100
> 
> Patch attached. Hope it's not too long, while still covering the gist of
> what was discussed in this thread.

Thanks, see some comments below.

> >From fb3fd3bef67de821c469c0edb5b1cd6680736565 Mon Sep 17 00:00:00 2001
> From: Joost Kremers <joostkremers@fastmail.com>
> Date: Mon, 11 Nov 2024 23:38:49 +0100
> Subject: [PATCH] Improve documentation for `while-let'.
> 
> * doc/lispref/control.texi: Improve documentation for `while-let`.

A nit: this should cite the name of the @node in which the changes
were made (in parens, as if it were a function).

> -@defmac if-let* varlist then-form else-forms...
> -Evaluate each binding in @var{varlist} in turn, like in @code{let*}
> +@defmac if-let* spec then-form else-forms...
> +Evaluate each binding in @var{spec} in turn, like in @code{let*}
>  (@pxref{Local Variables}), stopping if a binding value is @code{nil}.
>  If all are non-@code{nil}, return the value of @var{then-form},
>  otherwise the last form in @var{else-forms}.
>  @end defmac
>  
> -@defmac when-let* varlist then-forms...
> +@defmac when-let* spec then-forms...
>  Like @code{if-let*}, but without @var{else-forms}.
>  @end defmac
>  
> -@defmac and-let* varlist then-forms...
> +@defmac and-let* spec then-forms...
>  Like @code{when-let*}, but in addition, if there are no
>  @var{then-forms} and all the bindings evaluate to non-@code{nil}, return
>  the value of the last binding.
>  @end defmac

One "Like 'when-let*' is borderline-okay; 2 are too many.  Please
describe and-let* either completely stand-alone, without relying on
any prior macro, or as "like 'if-let*'" (since it is almost exactly
like if-let*).

> +Each element of the @code{spec} argument in these macros has the form
> +@code{(@var{symbol} @var{value-form})}: @var{value-form} is evaluated
> +and @var{symbol} is locally bound to the result.  As a special case,
> +@var{symbol} can be omitted if the result of @var{value-form} just needs
> +to be tested and there's no need to bind it to a variable.

This paragraph should be before the macros, not after them.

And the last sentence could use some simplification: it's quite a
mouthful, IMO.

> +A similar macro exists to run a loop until one binding evaluates to
> +@code{nil}:
> +
> +@defmac while-let spec then-forms...
> +Evaluate each binding in @var{spec} in turn, stopping if a binding value
> +is @code{nil}.  If all are non-@code{nil}, execute @var{then-forms},
> +then repeat the loop.  The return value is always @code{nil}.

The "repeat the loop" part should say explicitly that SPEC is
re-evaluated on each loop iteration.  AFAIU, this was the part which
confused people.

> +@code{while-let} replaces a pattern in which a binding is established
> +outside the @code{while}-loop, tested as part of the condition of
> +@code{while} and subsequently changed inside the loop using the same
> +expression that it was originally bound to:
> +
> +@example
> +(let ((result (do-computation)))
> +  (while result
> +    (do-stuff-with result)
> +    (setq result (do-computation))))
> +@end example
> +
> +Using @code{while-let}, this can be written more succinctly as:
> +
> +@example
> +(while-let ((result (do-computation)))
> +  (do-stuff-with result))
> +@end example
> +
> +One crucial difference here is the fact that in the first code example,
> +@code{result} is scoped outside the @code{wile} loop, whereas in the
> +second example, its scope is confined to the @code{while-let} loop.  As
> +a result, changing the value of @code{result} inside the loop has no
> +effect on the subsequent iteration.
> +@end defmac

This is too long a description.  For starters, I don't see the need to
justify the existence of the macro (we don't do that for the others).
The fact that it was the subject of a very long discussion doesn't
mean its documentation must be similarly long ;-)

Can we make this shorter and yet clear enough to improve what we have
there now?



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

* Re: Is this a bug in while-let or do I missunderstand it?
  2024-11-12 12:19                       ` Eli Zaretskii
@ 2024-11-12 12:45                         ` Joost Kremers
  2024-11-12 14:34                           ` Eli Zaretskii
  2024-11-12 23:45                         ` Joost Kremers
  1 sibling, 1 reply; 59+ messages in thread
From: Joost Kremers @ 2024-11-12 12:45 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: arthur.miller, ams, yuri.v.khan, emacs-devel

Hi Eli,

Thanks for your comments. I'll take them into account and come up with a
second version.

One question though:

On Tue, Nov 12 2024, Eli Zaretskii wrote:
[...]
> This is too long a description.

I can cut it down, but:

>  For starters, I don't see the need to
> justify the existence of the macro (we don't do that for the others).

Currently, before the description of `if-let*`, the manual contains an
explanation of the reason why `if-let*` and friends exist:

```
It can be convenient to bind variables in conjunction with using a
conditional.  It's often the case that you compute a value, and then
want to do something with that value if it's non-@code{nil}.  The
straightforward way to do that is to just write, for instance:

@example
(let ((result1 (do-computation)))
  (when result1
    (let ((result2 (do-more result1)))
      (when result2
        (do-something result2)))))
@end example

Since this is a very common pattern, Emacs provides a number of macros
to make this easier and more readable.  The above can be written the
following way instead:

@example
(when-let* ((result1 (do-computation))
            (result2 (do-more result1)))
  (do-something result2))
@end example

There's a number of variations on this theme, and they're briefly
described below.
```

Since `while-let` is a bit more complex, I thought I'd add a similar
explanation for it. I can try and shorten it, but I gather you think it
should be removed altogether?

> Can we make this shorter and yet clear enough to improve what we have
> there now?

I'll do my best. :-)

-- 
Joost Kremers
Life has its moments



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

* Re: Is this a bug in while-let or do I missunderstand it?
  2024-11-12 12:45                         ` Joost Kremers
@ 2024-11-12 14:34                           ` Eli Zaretskii
  2024-11-12 15:32                             ` Joost Kremers
  0 siblings, 1 reply; 59+ messages in thread
From: Eli Zaretskii @ 2024-11-12 14:34 UTC (permalink / raw)
  To: Joost Kremers; +Cc: arthur.miller, ams, yuri.v.khan, emacs-devel

> From: Joost Kremers <joostkremers@fastmail.fm>
> Cc: arthur.miller@live.com,  ams@gnu.org,  yuri.v.khan@gmail.com,
>   emacs-devel@gnu.org
> Date: Tue, 12 Nov 2024 13:45:50 +0100
> 
> >  For starters, I don't see the need to
> > justify the existence of the macro (we don't do that for the others).
> 
> Currently, before the description of `if-let*`, the manual contains an
> explanation of the reason why `if-let*` and friends exist:
> 
> ```
> It can be convenient to bind variables in conjunction with using a
> conditional.  It's often the case that you compute a value, and then
> want to do something with that value if it's non-@code{nil}.  The
> straightforward way to do that is to just write, for instance:
> 
> @example
> (let ((result1 (do-computation)))
>   (when result1
>     (let ((result2 (do-more result1)))
>       (when result2
>         (do-something result2)))))
> @end example
> 
> Since this is a very common pattern, Emacs provides a number of macros
> to make this easier and more readable.  The above can be written the
> following way instead:
> 
> @example
> (when-let* ((result1 (do-computation))
>             (result2 (do-more result1)))
>   (do-something result2))
> @end example
> 
> There's a number of variations on this theme, and they're briefly
> described below.
> ```
> 
> Since `while-let` is a bit more complex, I thought I'd add a similar
> explanation for it. I can try and shorten it, but I gather you think it
> should be removed altogether?

Doesn't the part you quoted cover while-let* as well?



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

* Re: Is this a bug in while-let or do I missunderstand it?
  2024-11-12 14:34                           ` Eli Zaretskii
@ 2024-11-12 15:32                             ` Joost Kremers
  0 siblings, 0 replies; 59+ messages in thread
From: Joost Kremers @ 2024-11-12 15:32 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: arthur.miller, ams, yuri.v.khan, emacs-devel

On Tue, Nov 12 2024, Eli Zaretskii wrote:
>> Since `while-let` is a bit more complex, I thought I'd add a similar
>> explanation for it. I can try and shorten it, but I gather you think it
>> should be removed altogether?
>
> Doesn't the part you quoted cover while-let* as well?

To a degree, but `while-let*` has some peculiarities that aren't covered.

Let me come up with a revised version and see what you think.

-- 
Joost Kremers
Life has its moments



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

* Re: Is this a bug in while-let or do I missunderstand it?
  2024-11-12 12:19                       ` Eli Zaretskii
  2024-11-12 12:45                         ` Joost Kremers
@ 2024-11-12 23:45                         ` Joost Kremers
  2024-11-13  9:45                           ` Sean Whitton
  1 sibling, 1 reply; 59+ messages in thread
From: Joost Kremers @ 2024-11-12 23:45 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: arthur.miller, ams, yuri.v.khan, emacs-devel

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

On Tue, Nov 12 2024, Eli Zaretskii wrote:
> Thanks, see some comments below.

Second proposal attached.

I've accounted for all of your comments. Some replies below:

>> >From fb3fd3bef67de821c469c0edb5b1cd6680736565 Mon Sep 17 00:00:00 2001
> One "Like 'when-let*' is borderline-okay; 2 are too many.  Please
> describe and-let* either completely stand-alone, without relying on
> any prior macro, or as "like 'if-let*'" (since it is almost exactly
> like if-let*).

I didn't write that, that was the documentation until now. :-) In the new
patch, I replaced these "Like 'when-let*'" with proper descriptions (worded
identically to the extent possible).

>> +Each element of the @code{spec} argument in these macros has the form
>> +@code{(@var{symbol} @var{value-form})}: @var{value-form} is evaluated
>> +and @var{symbol} is locally bound to the result.  As a special case,
>> +@var{symbol} can be omitted if the result of @var{value-form} just needs
>> +to be tested and there's no need to bind it to a variable.
>
> This paragraph should be before the macros, not after them.

I ended up putting it in the description of the first macro (`if-let*`) and
referring to it in the others, because the relevant variable (here SPEC) is
actually called VARLIST in `if-let*`, `when-let*` and `and-let*`, but SPEC
in while-let. (The non-starred versions also have SPEC, but they're being
deprecated. BTW, any reason why `while-let` isn't called `while-let*`?)

>> +@code{while-let} replaces a pattern in which a binding is established
>> +outside the @code{while}-loop, tested as part of the condition of
>> +@code{while} and subsequently changed inside the loop using the same
>> +expression that it was originally bound to:
>> +
>> +@example
>> +(let ((result (do-computation)))
>> +  (while result
>> +    (do-stuff-with result)
>> +    (setq result (do-computation))))
>> +@end example
>> +
>> +Using @code{while-let}, this can be written more succinctly as:
>> +
>> +@example
>> +(while-let ((result (do-computation)))
>> +  (do-stuff-with result))
>> +@end example
>> +
>> +One crucial difference here is the fact that in the first code example,
>> +@code{result} is scoped outside the @code{wile} loop, whereas in the
>> +second example, its scope is confined to the @code{while-let} loop.  As
>> +a result, changing the value of @code{result} inside the loop has no
>> +effect on the subsequent iteration.
>> +@end defmac
>
> This is too long a description.

I've taken it out completely, and put the most important points in the
description of `while-let` directly. Turns out, comparing `while-let` to
`while` may even be confusing, because the relevant bindings have different
scopes in the two snippets.

-- 
Joost Kremers
Life has its moments


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Improve-documentation-for-while-let.patch --]
[-- Type: text/x-patch, Size: 3718 bytes --]

From f426c242411b4538e913da31dac7b6b204c288e8 Mon Sep 17 00:00:00 2001
From: Joost Kremers <joostkremers@fastmail.com>
Date: Mon, 11 Nov 2024 23:38:49 +0100
Subject: [PATCH] Improve documentation for `while-let'.

* doc/lispref/control.texi: Improve documentation for `while-let`.
---
 doc/lispref/control.texi | 52 +++++++++++++++++++++++++++++-----------
 1 file changed, 38 insertions(+), 14 deletions(-)

diff --git a/doc/lispref/control.texi b/doc/lispref/control.texi
index 80ed2ce899b..40e23bc2453 100644
--- a/doc/lispref/control.texi
+++ b/doc/lispref/control.texi
@@ -322,32 +322,56 @@ Conditionals
 described below.
 
 @defmac if-let* varlist then-form else-forms...
-Evaluate each binding in @var{varlist} in turn, like in @code{let*}
-(@pxref{Local Variables}), stopping if a binding value is @code{nil}.
-If all are non-@code{nil}, return the value of @var{then-form},
-otherwise the last form in @var{else-forms}.
+Evaluate each binding in @var{varlist}, stopping if a binding value is
+@code{nil}.  If all are non-@code{nil}, return the value of
+@var{then-form}, otherwise the last form in @var{else-forms}.
+
+Each element of @code{varlist} has the form @code{(@var{symbol}
+@var{value-form})}: @var{value-form} is evaluated and @var{symbol} is
+locally bound to the result.  Bindings are sequential, as in @code{let*}
+(@pxref{Local Variables}).  As a special case, @var{symbol} can be
+omitted if only the test result of @var{value-form} is of interest:
+@var{value-form} is evaluated and checked for @code{nil}, but its value
+is not bound.
 @end defmac
 
 @defmac when-let* varlist then-forms...
-Like @code{if-let*}, but without @var{else-forms}.
+Evaluate each binding in @var{varlist}, stopping if a binding value is
+@code{nil}.  If all are non-@code{nil}, return the value of the last
+form in @var{then-forms}.  @var{varlist} has the same form as in
+@code{if-let*}.
 @end defmac
 
 @defmac and-let* varlist then-forms...
-Like @code{when-let*}, but in addition, if there are no
-@var{then-forms} and all the bindings evaluate to non-@code{nil}, return
-the value of the last binding.
-@end defmac
-
-@defmac while-let spec then-forms...
-Like @code{when-let*}, but repeat until a binding in @var{spec} is
-@code{nil}.  The return value is always @code{nil}.
+Evaluate each binding in @var{varlist}, stopping if a binding value is
+@code{nil}.  If all are non-@code{nil}, return the value of the last
+form in @var{then-forms}, or, if there are no @var{then-forms}, return
+the value of the last binding.  @var{varlist} has the same form as in
+@code{if-let*}.
 @end defmac
 
 Some Lisp programmers follow the convention that @code{and} and
-@code{and-let*} are for forms evaluated for return value, and
+@code{and-let*} are for forms evaluated for their return value, and
 @code{when} and @code{when-let*} are for forms evaluated for side-effect
 with returned values ignored.
 
+A similar macro exists to run a loop until one binding evaluates to
+@code{nil}:
+
+@defmac while-let spec then-forms...
+Evaluate each binding in @var{spec} in turn, stopping if a binding value
+is @code{nil}.  If all are non-@code{nil}, execute @var{then-forms},
+then repeat the loop.  Note that when the loop is repeated, the
+@var{value-forms} in @var{spec} are re-evaluated and the bindings are
+established anew.
+
+@var{spec} has the same form as the @var{varlist} argument in
+@code{if-let*}.  This means among other things that its bindings are in
+sequence, as with @code{let*} (@pxref{Local Variables}).
+
+The return value of @code{while-let} is always @code{nil}.
+@end defmac
+
 @node Combining Conditions
 @section Constructs for Combining Conditions
 @cindex combining conditions
-- 
2.47.0


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

* Re: Is this a bug in while-let or do I missunderstand it?
  2024-11-12 23:45                         ` Joost Kremers
@ 2024-11-13  9:45                           ` Sean Whitton
  2024-11-13  9:56                             ` Sean Whitton
  0 siblings, 1 reply; 59+ messages in thread
From: Sean Whitton @ 2024-11-13  9:45 UTC (permalink / raw)
  To: Joost Kremers; +Cc: Eli Zaretskii, arthur.miller, ams, yuri.v.khan, emacs-devel

Hello,

On Wed 13 Nov 2024 at 12:45am +01, Joost Kremers wrote:

> On Tue, Nov 12 2024, Eli Zaretskii wrote:
>> Thanks, see some comments below.
>
> Second proposal attached.

Please put the "like let*" back in (the first change in your patch).
There is one person who dislikes it but I think the average reader of
the Elisp reference will benefit.

> BTW, any reason why `while-let` isn't called `while-let*`?)

Just what it got named when it got added; probably a less-than-ideal
decision, with hindsight.  Unlike the if-let/when-let deprecation, it's
not worth renaming it.

-- 
Sean Whitton



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

* Re: Is this a bug in while-let or do I missunderstand it?
  2024-11-13  9:45                           ` Sean Whitton
@ 2024-11-13  9:56                             ` Sean Whitton
  2024-11-13 11:00                               ` Joost Kremers
  2024-11-14 21:51                               ` John ff
  0 siblings, 2 replies; 59+ messages in thread
From: Sean Whitton @ 2024-11-13  9:56 UTC (permalink / raw)
  To: Joost Kremers; +Cc: Eli Zaretskii, arthur.miller, ams, yuri.v.khan, emacs-devel

Hello,

On Wed 13 Nov 2024 at 05:45pm +08, Sean Whitton wrote:

> Please put the "like let*" back in (the first change in your patch).
> There is one person who dislikes it but I think the average reader of
> the Elisp reference will benefit.

Oops -- you just moved it.

I have one further comment on your patch:

>  Some Lisp programmers follow the convention that @code{and} and
> -@code{and-let*} are for forms evaluated for return value, and
> +@code{and-let*} are for forms evaluated for their return value, and
>  @code{when} and @code{when-let*} are for forms evaluated for side-effect
>  with returned values ignored.

This change renders the sentence grammatically incorrect.

It needs to be either

"for return value" and "for side-effect"

or

"for their return values" and "for the side-effects of their evaluation".

I think it's better to use the shorter one (i.e., make no changes here).

-- 
Sean Whitton



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

* Re: Is this a bug in while-let or do I missunderstand it?
  2024-11-13  9:56                             ` Sean Whitton
@ 2024-11-13 11:00                               ` Joost Kremers
  2024-11-13 12:17                                 ` Sean Whitton
  2024-11-14  7:55                                 ` Eli Zaretskii
  2024-11-14 21:51                               ` John ff
  1 sibling, 2 replies; 59+ messages in thread
From: Joost Kremers @ 2024-11-13 11:00 UTC (permalink / raw)
  To: Sean Whitton; +Cc: Eli Zaretskii, emacs-devel

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

On Wed, Nov 13 2024, Sean Whitton wrote:
> Hello,

> BTW, any reason why `while-let` isn't called `while-let*`?)

Just what it got named when it got added; probably a less-than-ideal
decision, with hindsight.  Unlike the if-let/when-let deprecation, it's
not worth renaming it.

Well, to me, the fact that `if-let*` c.s. are starred (and
the non-starred versions are deprecated) makes sense because their bindings
function the same way as those of `let*`. Given that, one might think that
the bindings of `while-let` function like those of `let`, which is not the
case. That, to me, would be reason enough to rename it. (Keeping `while-let`
as an alias, I guess.)

> On Wed 13 Nov 2024 at 05:45pm +08, Sean Whitton wrote:
>
>> Please put the "like let*" back in (the first change in your patch).
>> There is one person who dislikes it but I think the average reader of
>> the Elisp reference will benefit.
>
> Oops -- you just moved it.

Yeah, I decided to include it in the description of `if-let*`, not as a
separate paragraph, because someone looking up `if-let*` in the manual
might not actually read the text above the macro's description.

>>  Some Lisp programmers follow the convention that @code{and} and
>> -@code{and-let*} are for forms evaluated for return value, and
>> +@code{and-let*} are for forms evaluated for their return value, and
>>  @code{when} and @code{when-let*} are for forms evaluated for side-effect
>>  with returned values ignored.
>
> This change renders the sentence grammatically incorrect.
>
> It needs to be either
>
> "for return value" and "for side-effect"
>
> or
>
> "for their return values" and "for the side-effects of their evaluation".
>
> I think it's better to use the shorter one (i.e., make no changes here).

OK, updated patch attached.

-- 
Joost Kremers
Life has its moments


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Improve-documentation-for-while-let.patch --]
[-- Type: text/x-patch, Size: 3620 bytes --]

From c5009870df4738a4b1446aef7dc24488c8c956a4 Mon Sep 17 00:00:00 2001
From: Joost Kremers <joostkremers@fastmail.com>
Date: Mon, 11 Nov 2024 23:38:49 +0100
Subject: [PATCH] Improve documentation for `while-let'.

* doc/lispref/control.texi: Improve documentation for `while-let`.
---
 doc/lispref/control.texi | 50 +++++++++++++++++++++++++++++-----------
 1 file changed, 37 insertions(+), 13 deletions(-)

diff --git a/doc/lispref/control.texi b/doc/lispref/control.texi
index 80ed2ce899b..94eb4769ae0 100644
--- a/doc/lispref/control.texi
+++ b/doc/lispref/control.texi
@@ -322,25 +322,32 @@ Conditionals
 described below.
 
 @defmac if-let* varlist then-form else-forms...
-Evaluate each binding in @var{varlist} in turn, like in @code{let*}
-(@pxref{Local Variables}), stopping if a binding value is @code{nil}.
-If all are non-@code{nil}, return the value of @var{then-form},
-otherwise the last form in @var{else-forms}.
+Evaluate each binding in @var{varlist}, stopping if a binding value is
+@code{nil}.  If all are non-@code{nil}, return the value of
+@var{then-form}, otherwise the last form in @var{else-forms}.
+
+Each element of @code{varlist} has the form @code{(@var{symbol}
+@var{value-form})}: @var{value-form} is evaluated and @var{symbol} is
+locally bound to the result.  Bindings are sequential, as in @code{let*}
+(@pxref{Local Variables}).  As a special case, @var{symbol} can be
+omitted if only the test result of @var{value-form} is of interest:
+@var{value-form} is evaluated and checked for @code{nil}, but its value
+is not bound.
 @end defmac
 
 @defmac when-let* varlist then-forms...
-Like @code{if-let*}, but without @var{else-forms}.
+Evaluate each binding in @var{varlist}, stopping if a binding value is
+@code{nil}.  If all are non-@code{nil}, return the value of the last
+form in @var{then-forms}.  @var{varlist} has the same form as in
+@code{if-let*}.
 @end defmac
 
 @defmac and-let* varlist then-forms...
-Like @code{when-let*}, but in addition, if there are no
-@var{then-forms} and all the bindings evaluate to non-@code{nil}, return
-the value of the last binding.
-@end defmac
-
-@defmac while-let spec then-forms...
-Like @code{when-let*}, but repeat until a binding in @var{spec} is
-@code{nil}.  The return value is always @code{nil}.
+Evaluate each binding in @var{varlist}, stopping if a binding value is
+@code{nil}.  If all are non-@code{nil}, return the value of the last
+form in @var{then-forms}, or, if there are no @var{then-forms}, return
+the value of the last binding.  @var{varlist} has the same form as in
+@code{if-let*}.
 @end defmac
 
 Some Lisp programmers follow the convention that @code{and} and
@@ -348,6 +355,23 @@ Conditionals
 @code{when} and @code{when-let*} are for forms evaluated for side-effect
 with returned values ignored.
 
+A similar macro exists to run a loop until one binding evaluates to
+@code{nil}:
+
+@defmac while-let spec then-forms...
+Evaluate each binding in @var{spec} in turn, stopping if a binding value
+is @code{nil}.  If all are non-@code{nil}, execute @var{then-forms},
+then repeat the loop.  Note that when the loop is repeated, the
+@var{value-forms} in @var{spec} are re-evaluated and the bindings are
+established anew.
+
+@var{spec} has the same form as the @var{varlist} argument in
+@code{if-let*}.  This means among other things that its bindings are in
+sequence, as with @code{let*} (@pxref{Local Variables}).
+
+The return value of @code{while-let} is always @code{nil}.
+@end defmac
+
 @node Combining Conditions
 @section Constructs for Combining Conditions
 @cindex combining conditions
-- 
2.47.0


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

* Re: Is this a bug in while-let or do I missunderstand it?
  2024-11-13 11:00                               ` Joost Kremers
@ 2024-11-13 12:17                                 ` Sean Whitton
  2024-11-14  7:55                                 ` Eli Zaretskii
  1 sibling, 0 replies; 59+ messages in thread
From: Sean Whitton @ 2024-11-13 12:17 UTC (permalink / raw)
  To: Joost Kremers; +Cc: Eli Zaretskii, emacs-devel

Hello,

On Wed 13 Nov 2024 at 12:00pm +01, Joost Kremers wrote:

> OK, updated patch attached.

Thanks.  Let's give Eli a chance to look.

-- 
Sean Whitton



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

* Re: Is this a bug in while-let or do I missunderstand it?
  2024-11-13 11:00                               ` Joost Kremers
  2024-11-13 12:17                                 ` Sean Whitton
@ 2024-11-14  7:55                                 ` Eli Zaretskii
  2024-11-14  8:21                                   ` Joost Kremers
  1 sibling, 1 reply; 59+ messages in thread
From: Eli Zaretskii @ 2024-11-14  7:55 UTC (permalink / raw)
  To: Joost Kremers; +Cc: spwhitton, emacs-devel

> From: Joost Kremers <joostkremers@fastmail.fm>
> Cc: Eli Zaretskii <eliz@gnu.org>,  emacs-devel@gnu.org
> Date: Wed, 13 Nov 2024 12:00:36 +0100
> 
> >> +Each element of the @code{spec} argument in these macros has the form
> >> +@code{(@var{symbol} @var{value-form})}: @var{value-form} is evaluated
> >> +and @var{symbol} is locally bound to the result.  As a special case,
> >> +@var{symbol} can be omitted if the result of @var{value-form} just needs
> >> +to be tested and there's no need to bind it to a variable.
> >
> > This paragraph should be before the macros, not after them.
> 
> I ended up putting it in the description of the first macro (`if-let*`) and
> referring to it in the others, because the relevant variable (here SPEC) is
> actually called VARLIST in `if-let*`, `when-let*` and `and-let*`, but SPEC
> in while-let. (The non-starred versions also have SPEC, but they're being
> deprecated. BTW, any reason why `while-let` isn't called `while-let*`?)

The disadvantage of what you did is that you need to say repeatedly

  @var{varlist} has the same form as in @code{if-let*}.

and the reader has to go back, read the description of if-let* (which
she might not want to know anything about), and decide which parts of
that description are relevant to whatever macro she is interested in.
I frequently find such practices in a manual annoying, especially when
I need to consult it for some specific detail, and don't have time for
reading other parts which are of no interest to me.

We should always keep in mind that the most important use pattern of
the manual is as a reference, where the reader is interested only in
some very specific subject, so any need to read more than the minimum
is an annoyance to be avoided.

> +Each element of @code{varlist} has the form @code{(@var{symbol}
> +@var{value-form})}:

This form is better put in @w{..}, to prevent it being split between
lines.

Other than those nits, the new text LGTM, thanks.



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

* Re: Is this a bug in while-let or do I missunderstand it?
  2024-11-14  7:55                                 ` Eli Zaretskii
@ 2024-11-14  8:21                                   ` Joost Kremers
  0 siblings, 0 replies; 59+ messages in thread
From: Joost Kremers @ 2024-11-14  8:21 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: spwhitton, emacs-devel

On Thu, Nov 14 2024, Eli Zaretskii wrote:
>> From: Joost Kremers <joostkremers@fastmail.fm>
>> I ended up putting it in the description of the first macro (`if-let*`) and
>> referring to it in the others, because the relevant variable (here SPEC) is
>> actually called VARLIST in `if-let*`, `when-let*` and `and-let*`, but SPEC
>> in while-let. (The non-starred versions also have SPEC, but they're being
>> deprecated. BTW, any reason why `while-let` isn't called `while-let*`?)
>
> The disadvantage of what you did is that you need to say repeatedly
>
>   @var{varlist} has the same form as in @code{if-let*}.
>
> and the reader has to go back, read the description of if-let* (which
> she might not want to know anything about), and decide which parts of
> that description are relevant to whatever macro she is interested in.

Okay, so I'll put the explanation of VARLIST into the description of every
macro.

>> +Each element of @code{varlist} has the form @code{(@var{symbol}
>> +@var{value-form})}:
>
> This form is better put in @w{..}, to prevent it being split between
> lines.

Ok.

-- 
Joost Kremers
Life has its moments



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

* Re: Is this a bug in while-let or do I missunderstand it?
  2024-11-09 16:44                 ` Eli Zaretskii
                                     ` (2 preceding siblings ...)
  2024-11-09 18:07                   ` [External] : " Drew Adams
@ 2024-11-14 21:50                   ` John ff
  3 siblings, 0 replies; 59+ messages in thread
From: John ff @ 2024-11-14 21:50 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: Alfred M. Szmidt, Yuri Khan, arthur.miller, emacs-devel




-------- Original Message --------
From: Eli Zaretskii <eliz@gnu.org>
Sent: Sat Nov 09 16:44:28 GMT 2024
To: "Alfred M. Szmidt" <ams@gnu.org>
Cc: yuri.v.khan@gmail.com, arthur.miller@live.com, emacs-devel@gnu.org
Subject: Re: Is this a bug in while-let or do I missunderstand it?

> From: "Alfred M. Szmidt" <ams@gnu.org>
> Cc: arthur.miller@live.com, emacs-devel@gnu.org
> Date: Sat, 09 Nov 2024 11:33:45 -0500
> 
>        (while-let ((run (some-condition)))
> 	 (message "running"))
> 
>    Do you expect that to evaluate (some-condition) once, then, if it’s
>    initially true, run forever?
> 
> That is how it is described in the manual, so yes (some-condition)
> should only be done once, and not every iteration.  See (elisp)
> Conditionals .

Which could mean that the manual is wrong and needs to be fixed.

>       It can be convenient to bind variables in conjunction with using a
>    conditional.  It's often the case that you compute a value, and then
>    want to do something with that value if it's non-‘nil’.  The
>    straightforward way to do that is to just write, for instance:
>    
>         (let ((result1 (do-computation)))
>           (when result1
>             (let ((result2 (do-more result1)))
>               (when result2
>                 (do-something result2)))))
> 
>       Since this is a very common pattern, Emacs provides a number of
>    macros to make this easier and more readable.  The above can be written
>    the following way instead:
> 
> ... following the various with various FOO-let forms, ending with
> while-let.

The above description actually supports what Yuri was saying, not what
Arthur and you expect.





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

* Re: Is this a bug in while-let or do I missunderstand it?
  2024-11-13  9:56                             ` Sean Whitton
  2024-11-13 11:00                               ` Joost Kremers
@ 2024-11-14 21:51                               ` John ff
  2024-11-14 21:52                                 ` John ff
  1 sibling, 1 reply; 59+ messages in thread
From: John ff @ 2024-11-14 21:51 UTC (permalink / raw)
  To: Sean Whitton
  Cc: Joost Kremers, Eli Zaretskii, arthur.miller, ams, yuri.v.khan,
	emacs-devel




-------- Original Message --------
From: Sean Whitton <spwhitton@spwhitton.name>
Sent: Wed Nov 13 09:56:00 GMT 2024
To: Joost Kremers <joostkremers@fastmail.fm>
Cc: Eli Zaretskii <eliz@gnu.org>, arthur.miller@live.com, ams@gnu.org, yuri.v.khan@gmail.com, emacs-devel@gnu.org
Subject: Re: Is this a bug in while-let or do I missunderstand it?

Hello,

On Wed 13 Nov 2024 at 05:45pm +08, Sean Whitton wrote:

> Please put the "like let*" back in (the first change in your patch).
> There is one person who dislikes it but I think the average reader of
> the Elisp reference will benefit.

Oops -- you just moved it.

I have one further comment on your patch:

>  Some Lisp programmers follow the convention that @code{and} and
> -@code{and-let*} are for forms evaluated for return value, and
> +@code{and-let*} are for forms evaluated for their return value, and
>  @code{when} and @code{when-let*} are for forms evaluated for side-effect
>  with returned values ignored.

This change renders the sentence grammatically incorrect.

It needs to be either

"for return value" and "for side-effect"

or

"for their return values" and "for the side-effects of their evaluation".

I think it's better to use the shorter one (i.e., make no changes here).

-- 
Sean Whitton





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

* Re: Is this a bug in while-let or do I missunderstand it?
  2024-11-14 21:51                               ` John ff
@ 2024-11-14 21:52                                 ` John ff
  0 siblings, 0 replies; 59+ messages in thread
From: John ff @ 2024-11-14 21:52 UTC (permalink / raw)
  To: Sean Whitton
  Cc: Joost Kremers, Eli Zaretskii, arthur.miller, ams, yuri.v.khan,
	emacs-devel




-------- Original Message --------
From: John ff <jpff@codemist.co.uk>
Sent: Thu Nov 14 21:51:11 GMT 2024
To: Sean Whitton <spwhitton@spwhitton.name>
Cc: Joost Kremers <joostkremers@fastmail.fm>, Eli Zaretskii <eliz@gnu.org>, arthur.miller@live.com, ams@gnu.org, yuri.v.khan@gmail.com, emacs-devel@gnu.org
Subject: Re: Is this a bug in while-let or do I missunderstand it?




-------- Original Message --------
From: Sean Whitton <spwhitton@spwhitton.name>
Sent: Wed Nov 13 09:56:00 GMT 2024
To: Joost Kremers <joostkremers@fastmail.fm>
Cc: Eli Zaretskii <eliz@gnu.org>, arthur.miller@live.com, ams@gnu.org, yuri.v.khan@gmail.com, emacs-devel@gnu.org
Subject: Re: Is this a bug in while-let or do I missunderstand it?

Hello,

On Wed 13 Nov 2024 at 05:45pm +08, Sean Whitton wrote:

> Please put the "like let*" back in (the first change in your patch).
> There is one person who dislikes it but I think the average reader of
> the Elisp reference will benefit.

Oops -- you just moved it.

I have one further comment on your patch:

>  Some Lisp programmers follow the convention that @code{and} and
> -@code{and-let*} are for forms evaluated for return value, and
> +@code{and-let*} are for forms evaluated for their return value, and
>  @code{when} and @code{when-let*} are for forms evaluated for side-effect
>  with returned values ignored.

This change renders the sentence grammatically incorrect.

It needs to be either

"for return value" and "for side-effect"

or

"for their return values" and "for the side-effects of their evaluation".

I think it's better to use the shorter one (i.e., make no changes here).

-- 
Sean Whitton





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

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

Thread overview: 59+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-11-08 16:25 Is this a bug in while-let or do I missunderstand it? arthur miller
2024-11-08 19:23 ` Philip Kaludercic
2024-11-09  3:30   ` Sv: " arthur miller
2024-11-09  9:29 ` Yuri Khan
2024-11-09 13:03   ` Sv: " arthur miller
2024-11-09 13:15     ` Yuri Khan
2024-11-09 13:38       ` Sv: " arthur miller
2024-11-09 13:41         ` Yuri Khan
2024-11-09 13:47           ` Sv: " arthur miller
2024-11-09 14:04             ` Yuri Khan
2024-11-09 14:44               ` Sv: " arthur miller
2024-11-09 16:33               ` Alfred M. Szmidt
2024-11-09 16:44                 ` Eli Zaretskii
2024-11-09 16:53                   ` Eli Zaretskii
2024-11-09 17:33                   ` Andreas Schwab
2024-11-09 18:07                   ` [External] : " Drew Adams
2024-11-09 18:18                     ` Alfred M. Szmidt
2024-11-09 20:02                       ` Jens Schmidt
2024-11-09 20:38                         ` Alfred M. Szmidt
2024-11-09 21:18                           ` Joost Kremers
2024-11-10 11:44                         ` Alfred M. Szmidt
2024-11-10 12:24                           ` Better documentation for non-binding clauses of if-let and friends Jens Schmidt
2024-11-10 14:51                             ` Sean Whitton
2024-11-10 16:58                               ` Jens Schmidt
2024-11-11 10:03                               ` Alfred M. Szmidt
2024-11-11  8:20                             ` Alfred M. Szmidt
2024-11-09 19:32                     ` Sv: [External] : Re: Is this a bug in while-let or do I missunderstand it? arthur miller
2024-11-09 22:36                       ` Drew Adams
2024-11-09 22:53                         ` Drew Adams
2024-11-14 21:50                   ` John ff
2024-11-09 20:29                 ` Sv: " arthur miller
2024-11-10  6:22                   ` Eli Zaretskii
2024-11-10 10:40                     ` Joost Kremers
2024-11-10 12:10                       ` Alfred M. Szmidt
2024-11-10 19:49                         ` Sv: " arthur miller
2024-11-10 18:18                     ` arthur miller
2024-11-11  5:13                       ` Yuri Khan
2024-11-11  8:49                         ` Sv: " arthur miller
2024-11-11 12:23                           ` tomas
2024-11-11 22:41                     ` Joost Kremers
2024-11-12 12:19                       ` Eli Zaretskii
2024-11-12 12:45                         ` Joost Kremers
2024-11-12 14:34                           ` Eli Zaretskii
2024-11-12 15:32                             ` Joost Kremers
2024-11-12 23:45                         ` Joost Kremers
2024-11-13  9:45                           ` Sean Whitton
2024-11-13  9:56                             ` Sean Whitton
2024-11-13 11:00                               ` Joost Kremers
2024-11-13 12:17                                 ` Sean Whitton
2024-11-14  7:55                                 ` Eli Zaretskii
2024-11-14  8:21                                   ` Joost Kremers
2024-11-14 21:51                               ` John ff
2024-11-14 21:52                                 ` John ff
2024-11-09 21:47             ` Sv: " Joost Kremers
2024-11-09 22:07               ` Sv: " arthur miller
2024-11-10  6:07               ` Andreas Schwab
  -- strict thread matches above, loose matches on Subject: below --
2024-11-11  9:28 Better documentation for non-binding clauses of if-let and friends arthur miller
2024-11-11  9:58 ` Alfred M. Szmidt
2024-11-11 10:26   ` Joost Kremers

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).