* 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ messages in thread
end of thread, other threads:[~2024-11-14 21:52 UTC | newest] Thread overview: 56+ 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
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.