* Re: Is this a bug in while-let or do I missunderstand it?
@ 2024-11-12 3:36 arthur miller
2024-11-12 8:30 ` Joost Kremers
0 siblings, 1 reply; 17+ messages in thread
From: arthur miller @ 2024-11-12 3:36 UTC (permalink / raw)
To: joostkremers@fastmail.fm; +Cc: emacs-devel@gnu.org
[-- Attachment #1: Type: text/plain, Size: 1462 bytes --]
>+@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
The scope of the let-binding is the same in both. The crucial
difference is that in the first example, the user have control over
when and how 'result' is evaluated. The user can for example do:
(let ((result (do-computation)))
(while result
(do-stuff-with result)
(setq result (do-some-other-computation))))
Whereas in the other example, the code is automatically generated
to pass in the original condition calculation, and the user can not
interfere with the computation of the condition.
[-- Attachment #2: Type: text/html, Size: 5976 bytes --]
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: Is this a bug in while-let or do I missunderstand it? 2024-11-12 3:36 Is this a bug in while-let or do I missunderstand it? arthur miller @ 2024-11-12 8:30 ` Joost Kremers 2024-11-12 17:55 ` Alfred M. Szmidt 2024-11-12 23:08 ` arthur miller 0 siblings, 2 replies; 17+ messages in thread From: Joost Kremers @ 2024-11-12 8:30 UTC (permalink / raw) To: arthur miller; +Cc: emacs-devel@gnu.org On Tue, Nov 12 2024, arthur miller wrote: > The scope of the let-binding is the same in both. I don't see how. With `while`, `result` is let-bound outside the loop, with `while-let` it's bound inside the loop, even after macroexpanding it: ``` (macroexpand-all '(while-let ((result (foo))) (bar result))) ==> (catch 'done15 (while t (let* ((result (and t (foo)))) (if result (progn (bar result)) (throw 'done15 nil))))) ``` In fact, with `while`, you can use `result` even before you enter the loop: ``` (let ((result (do-something))) (do-something-with result) ... (while result (do-something-else-with result) (setq result (do-yet-another-thing)))) ``` So clearly the scope of `result` is wider. > The crucial > difference is that in the first example, the user have control over > when and how 'result' is evaluated. The user can for example do: > > (let ((result (do-computation))) > (while result > (do-stuff-with result) > (setq result (do-some-other-computation)))) Yes, and if that's what you need, you should use `while`. > Whereas in the other example, the code is automatically generated > to pass in the original condition calculation, and the user can not > interfere with the computation of the condition. Yes, but that's exactly the point of `while-let`. `while-let` is not there to replace `while`, it's a different macro for a different (though related) purpose. Mind you, what you want to do can still be done with `while-let`, provided you establish the relevant binding *outside* the loop: ``` (let ((continue t)) (while-let ((a (foo)) (b (bar)) ( continue)) (do-some-stuff) ... (when (stop-condition) (setq continue nil)))) ``` I think I understand where your confusion is coming from, and I've tried to address it in my proposed patch for the documentation. If you feel it's still not clear enough, I'll happily take another look. -- Joost Kremers Life has its moments ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: Is this a bug in while-let or do I missunderstand it? 2024-11-12 8:30 ` Joost Kremers @ 2024-11-12 17:55 ` Alfred M. Szmidt 2024-11-12 23:21 ` Sv: " arthur miller 2024-11-12 23:08 ` arthur miller 1 sibling, 1 reply; 17+ messages in thread From: Alfred M. Szmidt @ 2024-11-12 17:55 UTC (permalink / raw) To: Joost Kremers; +Cc: arthur.miller, emacs-devel > The scope of the let-binding is the same in both. I don't see how. With `while`, `result` is let-bound outside the loop, with `while-let` it's bound inside the loop, even after macroexpanding it: And that is (again) the crux of the matter, one group thinks it is LET bound outside of the loop, another inside. Consider this, which will pass nil to BAR, but also reassign RESULT to whatever FOO returns each iteration; which is the part that is confusing for those who consider LET to be the binding and scope of the variables (this also makes it much harder to debug code I think). (while-let ((result (foo))) (setq result nil) (bar result)) Which expands to: (catch 'done18 (while t (let* ((result (and t (foo)))) (if result (progn (setq result nil) (bar result)) (throw 'done18 nil))))) Mind you, what you want to do can still be done with `while-let`, provided you establish the relevant binding *outside* the loop: Then what point is while-let? Should there be a let-while? And a let-while-let? There are 20 occurences of while-let in Emacs ... ^ permalink raw reply [flat|nested] 17+ messages in thread
* Sv: Is this a bug in while-let or do I missunderstand it? 2024-11-12 17:55 ` Alfred M. Szmidt @ 2024-11-12 23:21 ` arthur miller 2024-11-12 23:31 ` Joost Kremers 0 siblings, 1 reply; 17+ messages in thread From: arthur miller @ 2024-11-12 23:21 UTC (permalink / raw) To: Alfred M. Szmidt, Joost Kremers; +Cc: emacs-devel@gnu.org [-- Attachment #1: Type: text/plain, Size: 702 bytes --] >> The scope of the let-binding is the same in both. > > I don't see how. With `while`, `result` is let-bound outside the loop, with > `while-let` it's bound inside the loop, even after macroexpanding it: > >And that is (again) the crux of the matter, one group thinks it is LET >bound outside of the loop, another inside. Yepp, you have put a finger on it. It is not so strange we have two views. It is a let-over-while that expands into while-over-let :-). Since the expansion get evaled, I guess we should see it as a while-over-let. The only confusing part is that we are illustrating it with let-over-while example and sort-of saying it is a shortcut to let-over-while. [-- Attachment #2: Type: text/html, Size: 3053 bytes --] ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: Sv: Is this a bug in while-let or do I missunderstand it? 2024-11-12 23:21 ` Sv: " arthur miller @ 2024-11-12 23:31 ` Joost Kremers 0 siblings, 0 replies; 17+ messages in thread From: Joost Kremers @ 2024-11-12 23:31 UTC (permalink / raw) To: arthur miller; +Cc: Alfred M. Szmidt, emacs-devel@gnu.org On Tue, Nov 12 2024, arthur miller wrote: >>> The scope of the let-binding is the same in both. >> >> I don't see how. With `while`, `result` is let-bound outside the loop, with >> `while-let` it's bound inside the loop, even after macroexpanding it: >> >>And that is (again) the crux of the matter, one group thinks it is LET >>bound outside of the loop, another inside. > > Yepp, you have put a finger on it. > > It is not so strange we have two views. It is a let-over-while that expands > into while-over-let :-). It's not a let-over-while. The let-over-while snippet was just used to explain why the macro was introduced, but (as you mention) it's misleading. That's why in my first proposal for the documentation, I mentioned the difference in scope explicitly. I've now taken that part out again, though, and am trying to explain `while-let` on its own, without reference to the `while` pattern it replaces. -- Joost Kremers Life has its moments ^ permalink raw reply [flat|nested] 17+ messages in thread
* Sv: Is this a bug in while-let or do I missunderstand it? 2024-11-12 8:30 ` Joost Kremers 2024-11-12 17:55 ` Alfred M. Szmidt @ 2024-11-12 23:08 ` arthur miller 1 sibling, 0 replies; 17+ messages in thread From: arthur miller @ 2024-11-12 23:08 UTC (permalink / raw) To: Joost Kremers; +Cc: emacs-devel@gnu.org [-- Attachment #1: Type: text/plain, Size: 2509 bytes --] >> The scope of the let-binding is the same in both. > >I don't see how. With `while`, `result` is let-bound outside the loop, with >`while-let` it's bound inside the loop, even after macroexpanding it: > >``` >(macroexpand-all '(while-let ((result (foo))) > (bar result))) > >==> > >(catch 'done15 > (while t > (let* > ((result > (and t > (foo)))) > (if result > (progn > (bar result)) > (throw 'done15 nil))))) >``` > >In fact, with `while`, you can use `result` even before you enter the loop: > >``` > >(let ((result (do-something))) > (do-something-with result) > ... > (while result > (do-something-else-with result) > (setq result (do-yet-another-thing)))) >``` > >So clearly the scope of `result` is wider. Indeed; I constantly forgett what while-let expands to. I had in mind a let-over-while, but while-let does not expand to let-over-while, but to a while. >> The crucial >> difference is that in the first example, the user have control over >> when and how 'result' is evaluated. The user can for example do: >> >> (let ((result (do-computation))) >> (while result >> (do-stuff-with result) >> (setq result (do-some-other-computation)))) > >Yes, and if that's what you need, you should use `while`. >Yes, but that's exactly the point of `while-let`. `while-let` is not there >to replace `while`, it's a different macro for a different (though related) >purpose. Yes. That was my conclusion too; it is its own construct. The name is perhaps a misnomer. >Mind you, what you want to do can still be done with `while-let`, provided >you establish the relevant binding *outside* the loop: > >``` >(let ((continue t)) > (while-let ((a (foo)) > (b (bar)) > ( continue)) > (do-some-stuff) > ... > (when (stop-condition) > (setq continue nil)))) >``` Or named-let. Perhaps bindings in spec should be rather called "named conditions" than let-bindings, because their nature is somewhat different from ordinary let-condiitions. >I think I understand where your confusion is coming from, and I've tried to >address it in my proposed patch for the documentation. If you feel it's >still not clear enough, I'll happily take another look To ge honest, I just disslike "-let" part of the "while-let", because it got me to think more of let, than of while. Can also be just me. [-- Attachment #2: Type: text/html, Size: 12094 bytes --] ^ permalink raw reply [flat|nested] 17+ messages in thread
* 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; 17+ 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] 17+ messages in thread
* Re: 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 3:30 ` Sv: " arthur miller 2024-11-09 9:29 ` Yuri Khan 1 sibling, 1 reply; 17+ 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] 17+ 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; 17+ 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] 17+ messages in thread
* Re: 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 2024-11-09 13:03 ` Sv: " arthur miller 1 sibling, 1 reply; 17+ 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] 17+ 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; 17+ 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] 17+ 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; 17+ 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] 17+ 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; 17+ 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] 17+ 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; 17+ 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] 17+ 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 ` Joost Kremers 0 siblings, 2 replies; 17+ 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] 17+ 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 ` Joost Kremers 1 sibling, 2 replies; 17+ 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] 17+ 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; 17+ 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] 17+ 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 20:29 ` Sv: " arthur miller 1 sibling, 1 reply; 17+ 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] 17+ 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 20:29 ` arthur miller 2024-11-10 6:22 ` Eli Zaretskii 0 siblings, 1 reply; 17+ 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] 17+ 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 2024-11-10 18:18 ` arthur miller 0 siblings, 2 replies; 17+ 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] 17+ 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 1 sibling, 1 reply; 17+ 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] 17+ 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; 17+ 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] 17+ 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; 17+ 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] 17+ 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 1 sibling, 1 reply; 17+ 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] 17+ 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; 17+ 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] 17+ 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; 17+ 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] 17+ 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; 17+ 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] 17+ 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 1 sibling, 0 replies; 17+ 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] 17+ messages in thread
end of thread, other threads:[~2024-11-12 23:31 UTC | newest] Thread overview: 17+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2024-11-12 3:36 Is this a bug in while-let or do I missunderstand it? arthur miller 2024-11-12 8:30 ` Joost Kremers 2024-11-12 17:55 ` Alfred M. Szmidt 2024-11-12 23:21 ` Sv: " arthur miller 2024-11-12 23:31 ` Joost Kremers 2024-11-12 23:08 ` arthur miller -- strict thread matches above, loose matches on Subject: below -- 2024-11-08 16:25 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 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-09 21:47 ` Joost Kremers
Code repositories for project(s) associated with this external index https://git.savannah.gnu.org/cgit/emacs.git https://git.savannah.gnu.org/cgit/emacs/org-mode.git This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.