emacs-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Sv: Is this a bug in while-let or do I missunderstand it?


From: arthur miller
Subject: Sv: Is this a bug in while-let or do I missunderstand it?
Date: Sat, 9 Nov 2024 03:30:40 +0000

>> (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

reply via email to

[Prev in Thread] Current Thread [Next in Thread]