|
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 |
[Prev in Thread] | Current Thread | [Next in Thread] |