From 7e3dcb8f7e65478a5c496784c6e328e86b7cccbe Mon Sep 17 00:00:00 2001 From: okamsn Date: Sun, 3 Sep 2023 13:16:17 -0400 Subject: [PATCH] Clean up documentation files. - Remove old changes from the list of recent changes in the README. - Add missing PR and Issue numbers to the change log. - Clean up examples. - In the README, stop saying that we are "experimenting" with `loopy-iter`. It was added over a year ago. - Briefly describe the default destructuring system in the README. - Clean up examples. - Mention place-wise optimization of accumulations in the Introduction of the Org doc. - Add examples for working around implementation details of `iter` command. - Add that it is an error to re-use iteration variables. - Add the paragraph of initial accumulation values to the concept index. - Fix `adjoin` example now that `equal` is the default test. - Remove `key` examples for now, since we know `key` is wrong. Will add back after fixing. Also remove from command signatures. - List `at` in `vconcat`'s command signature. - Remove `result-type` example from `collect`, now that deprecated. - Mention how `find` works when the loop is left early. --- CHANGELOG.md | 76 +++-- README.org | 74 +++-- doc/loopy-doc.org | 772 +++++++++++++++++++++++----------------------- 3 files changed, 469 insertions(+), 453 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a72be94e..6a2aa6dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ This document describes the user-facing changes to Loopy. ### Bugs Fixed - Rewrite `prepend` in terms of `append`. Rewrite `push-into` in terms of - `collect`. This change makes these commands work with the new system for + `collect` ([#160]). This change makes these commands work with the new system for optimized accumulation variables. - Without an explicit starting value for the accumulation variable, `reduce` now @@ -22,8 +22,8 @@ This document describes the user-facing changes to Loopy. (reduce i #'*)) ``` -- Fix `find` when `:on-failure` is nil. Previously, `nil` was interpreted as - not passing `:on-failure`. +- Fix `find` when `:on-failure` is nil ([#170]). Previously, `nil` was + interpreted as not passing `:on-failure`. ```emacs-lisp ;; Previously erroneously returned 27: @@ -34,10 +34,10 @@ This document describes the user-facing changes to Loopy. (finally-return val)) ``` -- Fix `find` when `EXPR` is nil and `:on-failure` is given. Previously, after - the test passed and `VAR` was set to `nil`, that `nil` was interpreted as not - passing the test, so that `VAR` then bound to the value passed for - `:on-failure`. +- Fix `find` when `EXPR` is nil and `:on-failure` is given ([#170]). + Previously, after the test passed and `VAR` was set to `nil`, that `nil` was + interpreted as not passing the test, so that `VAR` then bound to the value + passed for `:on-failure`. ```emacs-lisp ;; Previously erroneously returned 27: @@ -49,9 +49,12 @@ This document describes the user-facing changes to Loopy. ### Breaking Changes +- Fix how the first accumulated value is used in `reduce`. See [#164] and the + item above. + - Make it an error to re-use iteration variables with multiple iteration - commands. The resulting code shouldn't have worked anyway, but we now report - it as an error during macro expansion. + commands ([#142], [#144]). The resulting code shouldn't have worked anyway, + but we now report it as an error during macro expansion. ```elisp ;; Will now signal an error during expansion: @@ -78,17 +81,17 @@ This document describes the user-facing changes to Loopy. #### Deprecations -- Using multiple conditions in `always`, `never`, and `thereis` is deprecated. - These commands will be changed to have call argument lists more like - accumulation commands, such as `(always [VAR] VAL &key into)`. This will - simplify the code and remove an inconsistency between them and the other +- Using multiple conditions in `always`, `never`, and `thereis` is deprecated + ([#145], [#161]). These commands will be changed to have call argument lists + more like accumulation commands, such as `(always [VAR] VAL &key into)`. This + will simplify the code and remove an inconsistency between them and the other commands. -- `:result-type` is deprecated. This feature was taken from Common Lisp's - Iterate. While useful, this can be done more directly using named - accumulation variables (such as `loopy-result`) in special macro arguments, - such as `finally-return`. Because of `accum-opt`, using named variables is - more flexible. +- `:result-type` is deprecated ([#154], [#162]). This feature was taken from + Common Lisp's Iterate. While useful, this can be done more directly using + named accumulation variables (such as `loopy-result`) in special macro + arguments, such as `finally-return`. Because of `accum-opt`, using named + variables is more flexible. ```elisp ;; Can't be done using only `:result-type'. @@ -101,10 +104,10 @@ This document describes the user-facing changes to Loopy. (cl-coerce triples 'vector))) ``` -- `:init` is deprecated. Some commands had special behavior with `:init`, such - as `set-prev`, but this has been changed to work with `with` too. Some - iteration commands, such as `numbers`, change behavior based on whether - a variable is `with` bound. Removing `:init` increases consistency +- `:init` is deprecated ([#146], [#163]). Some commands had special behavior with + `:init`, such as `set-prev`, but this has been changed to work with `with` + too. Some iteration commands, such as `numbers`, change behavior based on + whether a variable is `with` bound. Removing `:init` increases consistency with these commands and decreases the duplication of features. - Relatedly, remove documentation that said `adjoin` supported `:init`. It does not. @@ -112,17 +115,17 @@ This document describes the user-facing changes to Loopy. ### Command Improvements - To produce faster code, some commands now avoid creating an intermediate - variable by initializing iteration variables to their first value. This - initialization can be controlled using the `with` special macro argument, - which can result in slower code. Previously, these iteration variables were - always initialized to `nil` and updated to the first value at the location of - the command. + variable by initializing iteration variables to their first value ([#142], + [#144]). This initialization can be controlled using the `with` special macro + argument, which can result in slower code. Previously, these iteration + variables were always initialized to `nil` and updated to the first value at + the location of the command. - `cycle` already has this behavior, but can now be slower. - `cons` will initialize to the list value. - `nums` and `seq-index` will initialize to the first numeric value. - The behavior of `always`, `never`, and `thereis` has been slightly changed to - be more convenient and consistent with other commands. + be more convenient and consistent with other commands ([#144]). - The commands now exit the loop without forcing a return value, which allows implicit return values to be finalized. - The commands now use variables to store the implicit return values of the @@ -142,15 +145,26 @@ This document describes the user-facing changes to Loopy. ``` - As with other incompatible commands, an error is now signaled when trying to use `thereis` with `always` or `never` **when using the same variable** + ([#144]). ### Other Changes - Add `loopy--other-vars`, given the more explicit restriction on - `loopy--iteration-vars`. For example, these are the variables bound by the - `set` command, which are allowed to occur in more than one command. - + `loopy--iteration-vars` ([#144]). For example, these are the variables bound + by the `set` command, which are allowed to occur in more than one command. + +[#144]: https://github.com/okamsn/loopy/issue/142 +[#144]: https://github.com/okamsn/loopy/pull/144 +[#145]: https://github.com/okamsn/loopy/issue/145 +[#146]: https://github.com/okamsn/loopy/issue/146 +[#154]: https://github.com/okamsn/loopy/issue/154 +[#160]: https://github.com/okamsn/loopy/pull/160 +[#161]: https://github.com/okamsn/loopy/pull/161 +[#162]: https://github.com/okamsn/loopy/pull/162 +[#163]: https://github.com/okamsn/loopy/pull/163 [#164]: https://github.com/okamsn/loopy/pull/164 [#165]: https://github.com/okamsn/loopy/pull/165 +[#170]: https://github.com/okamsn/loopy/pull/170 ## 0.11.2 diff --git a/README.org b/README.org index af4d4db2..8f8761a0 100644 --- a/README.org +++ b/README.org @@ -33,17 +33,17 @@ please let me know. - Unreleased: - The boolean commands =always=, =never=, and =thereis= now behave more like accumulation commands and use ~loopy-result~ by default. - - Re-using iteration variables in multiple iteration commands now signals - an error. - - By default, the commands =cons=, =iter=, =nums=, and =seq-index= - /can/ update named iteration variables outside of the main loop body - and initialize variables to non-nil values, producing faster code. - This can be overridden via the special macro argument =with=. - Using multiple conditions in =always=, =never=, and =thereis= is deprecated. These commands will be changed to have call argument lists more like accumulation commands, such as =(always [VAR] VAL &key into)=. This will simplify the code and remove an inconsistency between them and the other commands. + - Re-using iteration variables in multiple iteration commands now signals + an error. It never produced correct code. + - By default, the commands =cons=, =iter=, =nums=, and =seq-index= + /can/ update named iteration variables outside of the main loop body + and initialize variables to non-nil values, producing faster code. + This can be overridden via the special macro argument =with=. - =:result-type= is deprecated. Instead, use coercion functions in special macro arguments, possibly with =accum-opt=. - The =:init= keyword argument is deprecated. Use the special macro argument @@ -53,27 +53,6 @@ please let me know. storing the result of passing the first value and ~nil~ to the function. - The deprecated flags =lax-naming= and =split= were removed. - The deprecated command =sub-loop= was removed. - - Versions 0.11.1 and 0.11.2: None. Bug fixes. - - Version 0.11.0: - - More incorrect destructured bindings now correctly signal an error. - - Removed the variable ~loopy-first-iteration~. This can be replaced by just - explicitly using the command =set=. - - ~loopy-iter~ now works differently. See the changelog, manual, and - okamsn/loopy#119 for more info. - - The flag =lax-naming= is deprecated. Its behavior is now the default for - command aliases listed in ~loopy-iter-bare-commands~. - - The user option ~loopy-ignored-names~ is now deprecated. Instead of an - exclusive approach, it is replaced by an inclusive approach using the new - user options ~loopy-iter-bare-commands~ and - ~loopy-iter-bare-special-marco-arguments~. - - The command =sub-loop= is deprecated. Use the commands =loopy= and - =loopy-iter= instead. - - The =split= flag is deprecated. Use the more general and controllable - special macro argument =accum-opt= instead. - - ~loopy--accumulation-final-updates~ has been renamed to - ~loopy--vars-final-updates~. The old name is now an obsolete alias. - - Removed obsolete aliases for the variables ~loopy-aliases~ and - ~loopy-command-parsers~. - See the [[https://github.com/okamsn/loopy/blob/master/CHANGELOG.md][change log]] for less recent changes. # This auto-generated by toc-org. @@ -90,7 +69,7 @@ please let me know. * Introduction The ~loopy~ macro is used to generate code for a loop, similar to ~cl-loop~. -Unlike ~cl-loop~, ~loopy~ uses symbolic expressions instead of "clauses". +Unlike ~cl-loop~, ~loopy~ uses parenthetical expressions instead of "clauses". #+begin_src emacs-lisp ;; A simple usage of `cl-loop': @@ -106,6 +85,12 @@ Unlike ~cl-loop~, ~loopy~ uses symbolic expressions instead of "clauses". (collect evens i) (collect odds i)) (finally-return odds evens)) + + (loopy (numbers i :from 1 :to 10) + (if (cl-evenp i) + (collect i :into evens) + (collect i :into odds)) + (finally-return odds evens)) #+end_src ~loopy~ supports destructuring for iteration commands like =list= and @@ -137,7 +122,7 @@ accumulation commands like =sum= or =collect=. #+end_src The ~loopy~ macro is configurable and extensible. In addition to writing one's -own "loop commands" (such as =list= in the example below), by using "flags", one +own "loop commands" (such as =list= in the example above), by using "flags", one can choose whether to instead use ~pcase-let~, ~seq-let~, or even the Dash library for destructuring. @@ -166,7 +151,7 @@ Variables like =cars=, =cdrs=, and =digits= in the example above are automatically ~let~-bound so as to not affect code outside of the loop. ~loopy~ has arguments for binding (or not binding) variables, executing code -before/after the loop, executing code only if the loop completes, and for +before or after the loop, executing code only if the loop completes, and for setting the macro's return value (default ~nil~). This is in addition to the looping features themselves. @@ -175,38 +160,44 @@ iteration. * Similar Libraries -Loopy is not the only Lisp library that uses expressions instead of keyword -clauses (like in ~cl-loop~). [[https://common-lisp.net/project/iterate/][Iterate]] and [[https://github.com/Shinmera/for/][For]] are two examples from Common Lisp. +Loopy is not the only Lisp library that uses parenthetical expressions instead of +keyword clauses (like in ~cl-loop~). [[https://common-lisp.net/project/iterate/][Iterate]] and [[https://github.com/Shinmera/for/][For]] are two examples from +Common Lisp. #+begin_src emacs-lisp ;; Collecting 10 random numbers: - ;; cl-loop, iterate, for, loopy + ;; cl-loop (Emacs Lisp) (cl-loop repeat 10 collect (random 10)) + ;; loopy (Loopy) + (loopy (repeat 10) (collect (random 10))) + + ;; iterate (Common Lisp) (iterate (repeat 10) (collect (random 10))) + ;; for (Common Lisp) (for:for ((i repeat 10) (randoms collecting (random 10)))) - (loopy (repeat 10) (collect (random 10))) #+end_src Generally, all of the packages handle basic use cases in similar ways. One large difference is that ~iterate~ can embed its looping constructs in arbitrary -code. Loopy is currently experimenting with this feature using a separate -macro, ~loopy-iter~, which expands looping constructs using ~macroexpand~. +code. Loopy is currently provides this feature as a separate macro, +~loopy-iter~, which expands looping constructs using ~macroexpand~. #+begin_src emacs-lisp (require 'loopy-iter) ;; Things to node: - ;; - `accum-opt' produces more efficient accumulations + ;; - `accum-opt' produces more efficient accumulations for names variables ;; - `cycling' is another name for `repeat' ;; => ((-9 -8 -7 -6 -5 -4 -3 -2 -1) ;; (0) ;; (1 2 3 4 5 6 7 8 9 10 11)) (loopy-iter (accum-opt positives negatives zeroes) (numbering i :from -10 :to 10) + ;; Normal `let' and `pcase', not Loopy constructs: (let ((var (1+ i))) (pcase var ((pred cl-plusp) (collecting positives var)) @@ -252,6 +243,9 @@ Commands in Arbitrary Code]]), use * Multiple Kinds of Destructuring +The default destructuring system is a super-set of what =cl-lib= provides +and is described in the section [[https://github.com/okamsn/loopy/blob/master/doc/loopy-doc.org#basic-destructuring][Basic Destructuring]] in the documentation. + ~loopy~ can optionally use destructuring provided by ~pcase-let~, ~seq-let~, the =dash= library, as well as its own kind. This provides greater flexibility and allows you to use destructuring patterns that you're already familiar with. @@ -296,11 +290,13 @@ accumulation commands. #+end_src For more on how =dash= does destructuring, see their documentation on the [[https://github.com/magnars/dash.el#-let-varlist-rest-body][-let]] -form. +expression. * Loop Commands in Arbitrary Code -The macro ~loopy-iter~ can be used to embed loop commands in arbitrary code. +The macro ~loopy-iter~ can be used to embed loop commands in arbitrary code. It +is similar in use to Common Lisp's Iterate macro, but it is not a port of +Iterate to Emacs Lisp. #+begin_src emacs-lisp (require 'loopy-iter) diff --git a/doc/loopy-doc.org b/doc/loopy-doc.org index 6d57054c..cf45c5e0 100644 --- a/doc/loopy-doc.org +++ b/doc/loopy-doc.org @@ -99,7 +99,7 @@ destructuring. Upfront, the features provided are summarized below. They are described thoroughly later in this document. - ~loopy~ :: A macro similar to ~cl-loop~. Unlike ~cl-loop~, ~loopy~ uses - parenthesized expressions instead of "clauses". + parenthetical forms instead of keyword "clauses". #+begin_src emacs-lisp ;; A simple usage of `cl-loop': @@ -140,7 +140,7 @@ described thoroughly later in this document. #+begin_src emacs-lisp ;; => (1 2 3 (:k1 4) 4) - (loopy-let* (((a b c &rest d &key k1) (list 1 2 3 :k1 4))) + (loopy-let* (((a b c &rest d &key k1) '(1 2 3 :k1 4))) (list a b c d k1)) ;; => ((7 2 8) [9 2 10]) @@ -148,13 +148,17 @@ described thoroughly later in this document. (my-vector (vector 1 2 3))) (loopy-ref (((a _ c) my-list) ([d _ e] my-vector)) - (setf a 7 c 8 d 9 e 10) + (setf a 7 + c 8 + d 9 + e 10) (list my-list my-vector))) #+end_src Some other things to note are: -- ~loopy~ (and so ~loopy-iter~) support destructuring for both iteration and + +- ~loopy~ (and so ~loopy-iter~) supports destructuring for both iteration and accumulation commands. #+begin_src emacs-lisp @@ -174,7 +178,7 @@ Some other things to note are: (finally-return elem1 elem2)) #+end_src -- The looping macros are configurable and extensible. One can create their own +- The looping macros are configurable and extensible. One can create one's own loop commands and command aliases. "Flags" can be used to configure the looping macros' behavior, such as by using an alternative destructuring system like =pcase= or the Dash library. @@ -192,14 +196,13 @@ Some other things to note are: (finally-return digits cars cdrs)) #+end_src - - Compared to ~cl-loop~, more constructs are provided for how loops are - completed and values returned. For example, the =leave= command will exit the - loop without changing what would be returned. This is a more generic form of - =while= and =until=, though those are also provided. =after-do= - (a.k.a. =else-do=) is a construct that runs Lisp code only when the loop - completes successfully, similar to Python's ~else~ statement after ~for~ and - ~while~ loops. + completed and how values are returned. For example, the =leave= command will + exit the loop without changing what would be returned. This is a more generic + form of the commands =while= and =until=, though those are also provided. + =after-do= (a.k.a. =else-do=) is a construct that runs Lisp code only when the + loop completes successfully, similar to Python's ~else~ statement after ~for~ + and ~while~ loops. #+begin_src emacs-lisp ;; => (1 3 5) @@ -218,21 +221,33 @@ Some other things to note are: (finally-return always-run run-when-complete)) #+end_src -- One can declare which accumulations should be optimized (and so treated as - implied variables). This can make accumulation into multiple named variables - much faster. +- One can declare which accumulations variables should be optimized (and so + treated as user-inaccessible during the loop). This can make accumulation + into multiple named variables much faster. As needed, can make Loopy optimize + for at-start accumulations or at-end accumulations. #+begin_src emacs-lisp ;; Expands into the efficient `push'-`nreverse' idiom, not ;; the `nonc'-`list' idiom that would be used by `cl-loop'. + ;; => ((1 3) (2 4)) (loopy (accum-opt cars cdrs) (array elem [(1 . 2) (3 . 4)]) (collect (cars . cdrs) elem) (finally-return cars cdrs)) + + ;; Prioritizes collecting at the start of `my-var'. + ;; => (5 3 1 4 6) + (loopy (accum-opt (my-var start)) + (array (car . cdr) [(1 . 2) (3 . 4) (5 . 6)]) + (collect my-var car :at start) + (when (> cdr 2) + (collect my-var cdr :at end)) + (finally-return my-var)) #+end_src -That being said, Loopy is not yet feature complete. Please request features or -report problems in this project’s [[https://github.com/okamsn/loopy/issues][issues tracker]]. + +All that being said, Loopy is not yet feature complete. Please request features +or report problems in this project’s [[https://github.com/okamsn/loopy/issues][issues tracker]]. * Basic Concepts :PROPERTIES: @@ -254,9 +269,9 @@ opposed to "loop commands" ([[#macro-arguments][Special Macro Arguments]]). command =list= in the expression =(list i '(1 2 3))=. A command inserts code into the loop body, but can also perform additional setup like initializing variables. Many commands set a condition for ending the loop. In the case of -=list=, the command iterates through the elements of a list, binding the -variable ~i~ to each element. After iterating through all elements, the loop is -forced to end. +=list= in the above expression, the command iterates through the elements of a +list, binding the variable ~i~ to each element. After iterating through all +elements, the loop is forced to end. In general, a loop ends when any looping condition required by a loop command evaluates to ~nil~. If no conditions are needed, then the loop runs infinitely @@ -267,9 +282,9 @@ stated explicitly, as in one of the early-exit commands or part of the =finally-return= macro argument, or come from accumulating loop commands using an implied accumulation variable ([[#accumulation-commands][Accumulation Commands]]). -The ~loopy~ macro is configurable. One can add custom commands -([[#adding-custom-commands][Custom Commands]]), add custom command aliases ([[#custom-aliases][Custom Aliases]]), and specify -macro options for a particular loop ([[#flags][Using Flags]]). Each of these ideas is +The ~loopy~ macro is configurable. One can add custom commands ([[#adding-custom-commands][Custom +Commands]]), add custom command aliases ([[#custom-aliases][Custom Aliases]]), and specify macro +options for a particular loop ([[#flags][Using Flags]]). Each of these features is explained in detail later in this document. * Special Macro Arguments @@ -281,7 +296,7 @@ explained in detail later in this document. #+cindex: special macro argument There are only a few special macro arguments. If a macro argument does not match one of these special few, ~loopy~ will attempt to interpret it as a loop -command, and signal an error if that fails. +command, and will signal an error if that fails. These special macro arguments are always processed before loop commands, regardless of the order of the arguments passed to ~loopy~. @@ -341,12 +356,14 @@ regardless of the order of the arguments passed to ~loopy~. ;; Without `without', `loopy' would try to initialize `a' to nil, which would ;; overwrite the value of 5 above. + ;; => (5 4 3 2 1) (let ((a 5)) (loopy (without a) ; Don't initialize `a'. (until (zerop a)) ; Leave loop when `a' equals 0. (collect a) ; Collect the value of `a' into a list. (set a (1- a)))) ; Set `a' to the value of `(1- a)'. + ;; => (5 4 3 2 1) (let ((a 5)) (loopy (no-init a) (while (not (zerop a))) @@ -362,7 +379,7 @@ regardless of the order of the arguments passed to ~loopy~. before the loop starts, after variables are initialized. #+begin_src emacs-lisp - ;; = > (6 7 8) + ;; => (6 7 8) (loopy (with (a 1) (b 2)) ; Set `a' to 1 and `b' to 2. (before-do (cl-incf a) ; Add 1 to `a'. (cl-incf b)) ; Add 1 to `b'. @@ -381,9 +398,10 @@ regardless of the order of the arguments passed to ~loopy~. #+findex: after #+findex: else-do #+findex: else -- =after-do=, =after=, =else-do=, =else= :: Run Lisp expressions after the - loop successfully completes. This is similar to Python’s ~else~ statement - following a ~for~ or ~while~ loop. +- =after-do=, =after=, =else-do=, =else= :: Run Lisp expressions after the loop + successfully completes. This is similar to Python’s ~else~ statement + following a ~for~ or ~while~ loop. Unlike ~progn~, the return values of the + expressions _do not_ affect the return value of the macro. #+begin_src emacs-lisp ;; Messages that no odd number was found: @@ -410,7 +428,8 @@ regardless of the order of the arguments passed to ~loopy~. #+findex: finally-do #+findex: finally - =finally-do=, =finally= :: Run Lisp expressions after the loop exits, always. - These expressions _do not_ affect the final return value of the loop. + Unlike ~progn~, the return values of the expressions _do not_ affect the + return value of the macro. #+begin_src emacs-lisp ;; => (nil finally) @@ -624,11 +643,11 @@ and this is not: #+END_SRC Trying to use loop commands in places where they don't belong will result in -errors when the code is evaluated. +errors while the macro is expanding and when the code is evaluated. You should keep in mind that commands are evaluated in order. This means that -attempting to do something like the below example might not do what you expect, -as =i= is assigned a value from the list after collecting =i= into =coll=. +attempting something like the below example might not do what you expect, as =i= +is assigned a value from the list after collecting =i= into =coll=. #+caption: An example of how loop commands are evaluated in order. #+BEGIN_SRC emacs-lisp @@ -645,8 +664,9 @@ Similary, the =array= command has the alias =string=, because the =array= command can be used to iterate through the elements of an array or string[fn:1]. You can define custom aliases using the macro ~loopy-defalias~ ([[#custom-aliases][Custom Aliases]]). -Similar to other libraries, many commands have an alias of the present -participle form (the "-ing" form). A few examples are seen in the table below. +Similar to other libraries, many commands have an alias of the +present-participle form (the "-ing" form). A few examples are seen in the table +below. | Command | "-ing" Alias | |-----------+--------------| @@ -710,7 +730,7 @@ for using Loopy's destructuring outside of ~loopy~ loops ([[#destr-macros]]). This section describes the basic built-in destructuring used by most loop commands, such as =set= and =list=. Destructuring in accumulation commands and -sequence reference commands works slightly differently, and is described more in +sequence-reference commands works slightly differently, and is described more in those sections. The last thing to note is that ~loopy~ loops can be made to use alternative @@ -772,17 +792,18 @@ An element in the sequence =VAR= can be one of the following: (collect (list i j k))) #+end_src -- The symbol =_=: The symbol =_= (an underscore) means to avoid creating a +- The symbol =_= (an underscore): The symbol =_= means to avoid creating a variable. This can be more efficient. #+begin_src emacs-lisp - ;; Only create the variables `a' and `c'. - ;; => ((1 3) (4 6)) - (loopy (list (a _ c) '((1 2 3) (4 5 6))) - (collect (list a c))) + ;; Only creates the variables `a' and `d': + ;; => ((1 4) (5 8)) + (loopy (list (a _ _ d) '((1 2 3 4) (5 6 7 8))) + (collect (list a d))) ;; These two destructurings do the same thing, ;; and only bind the variable `a': + ;; ;; => (1 3) (loopy (array (a) [(1 2) (3 4)]) (collect a)) @@ -801,6 +822,7 @@ An element in the sequence =VAR= can be one of the following: #+begin_src emacs-lisp ;; See that the variable `both' holds the value of the entire ;; list element: + ;; ;; => (((1 2) 1 2) ;; ((3 4) 3 4)) (loopy (list (&whole both i j) '((1 2) (3 4))) @@ -813,9 +835,9 @@ An element in the sequence =VAR= can be one of the following: - The symbol =&rest=: A variable named after =&rest= contains the remaining elements of the destructured value. When destructuring lists, one can also - instead use dotted notation. These variables can themselves be sequences. + use dotted notation. These variables can themselves be sequences. - This is the same as when used in ~seq-let~. + This =&rest= is the same as when used in ~seq-let~. #+begin_src emacs-lisp ;; => ((1 [2 3]) (4 [5 6])) @@ -827,19 +849,24 @@ An element in the sequence =VAR= can be one of the following: (collect (list i j k))) ;; => ((1 (2 3)) (4 (5 6))) - (loopy (list (i . j) '((1 2 3) (4 5 6))) - (collect (list i j))) - - ;; Works the same as above: (loopy (list (i &rest j) '((1 2 3) (4 5 6))) (collect (list i j))) - ;; The above using `seq-let': - (let ((result)) - (dolist (elem '((1 2 3) (4 5 6))) - (seq-let [i &rest j] elem - (push (list i j) result))) - (reverse result)) + ;; => ((1 2 3) (4 5 6)) + (loopy (list (i &rest (j k)) '((1 2 3) (4 5 6))) + (collect (list i j k))) + + ;; => ((1 2 3) (4 5 6)) + (loopy (list (i . (j k)) '((1 2 3) (4 5 6))) + (collect (list i j k))) + + ;; => ((1 2 3) (4 5 6)) + (loopy (list (i &rest [j k]) '((1 . [2 3]) (4 . [5 6]))) + (collect (list i j k))) + + ;; => ((1 2 3) (4 5 6)) + (loopy (list (i . [j k]) '((1 . [2 3]) (4 . [5 6]))) + (collect (list i j))) #+end_src - The symbol =&key= or =&keys=: Variables named after =&key= are transformed @@ -861,7 +888,8 @@ An element in the sequence =VAR= can be one of the following: ~nil~ for a key is not the same as a missing key. #+begin_src emacs-lisp - ;; Note that `nil' is not the same as a missing value. + ;; Note that `nil' is not the same as a missing value: + ;; ;; => ((1 2 nil 25) (4 5 24 25)) (loopy (list (&key a b (c 24) (missing 25)) '((:b 2 :c nil :a 1) (:a 4 :b 5))) @@ -874,11 +902,13 @@ An element in the sequence =VAR= can be one of the following: #+begin_src emacs-lisp ;; Keys are only sought after positional variables: + ;; ;; => ((1 2 :k1 'ignored 3)) (loopy (array (a b c d &key k1) [(1 2 :k1 'ignored :k1 3)]) (collect (list a b c d k1))) ;; If `&rest' is used, keys are sought only in that variable: + ;; ;; => ((1 (:k1 3) 3)) (loopy (array (a &rest b &key k1) [(1 :k1 3)]) (collect (list a b k1))) @@ -905,6 +935,10 @@ An element in the sequence =VAR= can be one of the following: :CUSTOM_ID: commands-for-generic-evaluation :END: +These generic commands are for settings values and running sub-commands or +sub-expressions during the loop. These commands do not affect macro's return +value and do no affect how the loop iterates. + #+findex: do - =(do EXPRS)= :: Evaluate multiple Lisp expressions, like a =progn=. @@ -920,46 +954,6 @@ An element in the sequence =VAR= can be one of the following: (do (message "%d" i))) #+END_SRC -#+findex: set -#+findex: setting -#+findex: expr -#+findex: exprs -- =(set|expr VAR [EXPRS])= :: Bind =VAR= to each =EXPR= in order. - Once the last =EXPR= is reached, it is used repeatedly for the rest of the - loop. With no =EXPR=, =VAR= is repeatedly bound to ~nil~. - - This command also has the aliases =setting= and =exprs=. - - #+ATTR_TEXINFO: :tag Note - #+begin_quote - =set= does /not/ behave the same as ~setq~ in all situations. - - While =set= can take multiple arguments, it only assigns the value of one - expression to one variable during each iteration of the loop (unless using - destructuring). It does not take pairs of variables and values in the same - way that ~setq~ does. - - Furthermore, variables assigned by =set= (and other commands) are by - default ~let~-bound around the loop and generally initialized to ~nil~. - This means that doing =(set VAR EXPR)= will not, by default, affect - variables outside of the loop in the same way that using =(do (setq VAR - EXPR))= would. - #+end_quote - - #+BEGIN_SRC emacs-lisp - ;; => '(1 2 3 3 3) - (loopy (cycle 5) - (set i 1 2 3) - (collect coll i) - (finally-return coll)) - - ;; => '(0 1 2 3 4) - (loopy (cycle 5) - (set i 0 (1+ i)) - (collect coll i) - (finally-return coll)) - #+END_SRC - #+findex: group #+findex: command-do - =(group|command-do [CMDS])= :: Evaluate multiple loop commands, as if in a @@ -990,14 +984,46 @@ An element in the sequence =VAR= can be one of the following: (sum i))) #+end_src +#+findex: set +#+findex: setting +#+findex: expr +#+findex: exprs +- =(set|expr VAR [EXPRS])= :: Bind =VAR= to each =EXPR= in order. Once the last + =EXPR= is reached, it is used repeatedly for the rest of the loop. With no + =EXPR=, =VAR= is bound to ~nil~ during each iteration of the loop. + + This command also has the aliases =setting= and =exprs=. + + Unlike the Emacs Lisp function ~set~, the variable name should not be quoted. + Unlike the Emacs Lisp special form ~setq~, the command =set= only sets one + variable, and this variable is by default ~let~-bound around the loop. To + stop =VAR= from being ~let~-bound around the loop, use the special macro + argument =without= ([[#macro-arguments]]). + + #+BEGIN_SRC emacs-lisp + ;; => '(1 2 3 3 3) + (loopy (cycle 5) + (set i 1 2 3) + (collect coll i) + (finally-return coll)) + + ;; => '(0 1 2 3 4) + (loopy (cycle 5) + (set i 0 (1+ i)) + (collect coll i) + (finally-return coll)) + #+END_SRC + #+findex: set-prev #+findex: setting-prev #+findex: prev-set #+findex: prev-expr #+findex: prev - =(set-prev|prev-expr VAR VAL &key back)= :: Bind =VAR= to a value =VAL= from a - previous cycle in the loop. With =BACK=, use the value from that many cycles - previous. This command /does not/ work like a queue. + previous cycle in the loop. With =BACK= (default: 1), use the value from that + many cycles previous. This command /does not/ work like a queue; it always + uses the value from the =BACK=-th previous cycle, regardless of when the + command is run. This command also has the aliases =setting-prev=, =prev-set=, and =prev=. @@ -1041,20 +1067,19 @@ can be exited by using early-exit commands ([[#exiting-the-loop-early]]) or bool commands ([[#boolean-commands]]). Iteration commands must occur in the top level of the ~loopy~ form or in a -sub-loop command ([[#sub-loops]]). Trying to do something like the below will -signal an error. +sub-loop command ([[#sub-loops]]). Using them elsewhere and trying to do something +like the below example will signal an error. #+begin_src emacs-lisp ;; Signals an error: (loopy (list i '(1 2 3 4 5)) (when (cl-evenp i) - ;; Can't use `list' in a `when'. + ;; Can't use `list' inside `when'. ;; Will signal an error. (list j '(6 7 8 9 10)) (collect j))) #+end_src - In ~loopy~, iteration commands are named after what they iterate through. For example, the =array= and =list= commands iterate through the elements of arrays and lists, respectively. @@ -1067,6 +1092,16 @@ initial value of an iteration variable can be set using the =with= special macro argument, but this can result in less efficient code. #+end_quote +Because some iteration commands use their variable to manage state, it is an +error to use the same iteration variable for multiple iteration commands. + +#+begin_src emacs-lisp + ;; Signals an error due to the re-use of `i': + (loopy (numbers i :from 1 :to 10) + (list i '(1 2 3)) + (finally-return t)) +#+end_src + *** Generic Iteration :PROPERTIES: :CUSTOM_ID: generic-iteration @@ -1129,18 +1164,6 @@ argument, but this can result in less efficient code. This command also has the name =iterating=. - #+ATTR_TEXINFO: :tag Warning - #+begin_quote - The loop ends when the iterator finishes, which must be checked before the - code in loop body is run. To check whether a iterator is finished, ~loopy~ - checks whether it signals an error when trying to yield a value. - - Because values are yielded before the next iteration step of the loop, trying - to yield more values from the iterator after the loop ends will result in lost - values. One option for working around this is to use the generic command - =set= with the function ~iter-next~ directly. - #+end_quote - #+begin_src emacs-lisp ;; With var: ;; => ((1 . 4) (2 . 5) (3 . 6)) @@ -1153,16 +1176,55 @@ argument, but this can result in less efficient code. ;; Without var: ;; => (1 2 3) - (loopy (with (iter-maker (iter-lambda () - ;; These yielded values are all ignored. - (iter-yield 'first-yield) - (iter-yield 'second-yield) - (iter-yield 'third-yield)))) - (iter (funcall iter-maker)) + (loopy (iter (funcall (iter-lambda () + ;; These yielded values are all ignored. + (iter-yield 'first-yield) + (iter-yield 'second-yield) + (iter-yield 'third-yield)))) (set i 1 (1+ i)) (collect i)) #+end_src + #+ATTR_TEXINFO: :tag Warning + #+begin_quote + Be aware that values are yielded from the iterator before running the loop + body. When the iterator can no longer yield values, it is finished. + + Because values are yielded before the next iteration step of the loop, trying + to yield more values from the iterator after the loop ends will result in lost + values. One option for working around this is to use the generic command + =set= with the function ~iter-next~ directly. + #+end_quote + + #+begin_src emacs-lisp + ;; => 5, not 4 as one might expect. + (loopy (with (iter-obj (funcall (iter-lambda () + (let ((i 0)) + (while t + (iter-yield (cl-incf i)))))))) + (iter iter-obj :close nil) + (cycle 3) + (finally-return (prog1 (iter-next iter-obj) + (iter-close iter-obj)))) + + ;; Avoiding missed yielded values: + ;; + ;; => ((1 2 3) 4) + (loopy (with (iter-obj (funcall (iter-lambda () + (let ((i 0)) + (while t + (iter-yield (cl-incf i))))))) + (j nil)) + (cycle 3) + (set j (condition-case nil + (iter-next iter-obj) + (iter-end-of-sequence nil))) + (collect j) + (finally-return (prog1 + (list loopy-result (iter-next iter-obj)) + (iter-close iter-obj)))) + #+end_src + *** Numeric Iteration :PROPERTIES: @@ -1185,8 +1247,9 @@ variants =numbers-up= and =numbers-down=. This command also has the aliases =num=, =number=, and =numbering=. The command =numbers= is used to iterate through numbers. For example, - =(numbers i :from 1 :to 10)= is similar to =(list i (number-sequence 1 10))=, - and =(numbers i 3)= is similar to =(set i 3 (1+ i))=. + =(numbers i :from 1 :to 10)= is similar to the command + =(list i (number-sequence 1 10))=, and =(numbers i 3)= is similar to + =(set i 3 (1+ i))=. For efficiency, =VAR= is initialized to the starting numeric value, not ~nil~, and is updated at the end of each step of the loop. This can be overridden @@ -1425,7 +1488,8 @@ source sequences. =KEYS= is one or several of =from=, =upfrom=, =downfrom=, =to=, =upto=, =downto=, =above=, =below=, =by=, and =index=. =index= names the variable - used to store the index being accessed. For others, see the =numbers= command. + used to store the index being accessed. For others, see the =numbers= + command. If multiple arrays are given, then the elements of these arrays are distributed into an array of lists. In that case, the above keywords apply to @@ -1483,7 +1547,7 @@ source sequences. #+findex: listing #+findex: each - =(list|each VAR EXPR [EXPRS] &key by)= :: Loop through each element of the - list =EXPR=. Optionally, update the list by =by= instead of =cdr=. + list =EXPR=. Optionally, update the list using =by= instead of =cdr=. This command also has the alias =listing=. @@ -1569,7 +1633,7 @@ source sequences. (finally-return keys values))) #+end_src - Depending on how a map is created, a map might repeat a key multiple times. + Depending on how a map is created, a map might contain a key multiple times. Currently, the function ~map-pairs~ returns such keys. By default, the ~loopy~ command =map-pairs= ignores such duplicate keys. This is for two reasons: @@ -1608,16 +1672,24 @@ source sequences. #+findex: seqing #+findex: sequencing - =(sequence|seq VAR EXPR [EXPRS] &key KEYS)= :: Loop through the sequence - =EXPR=, binding =VAR= to the elements of the sequence (see [[info:elisp#Sequences Arrays Vectors][elisp#Sequences - Arrays Vectors]]). This is a more generic form of the commands =list= and - =array=, though it is somewhat less efficient. These sequences should not be - confused with those generic sequences as understood by the library =seq.el=. + =EXPR=, binding =VAR= to the elements of the sequence (a list or an array). + Because it is more generic, =sequence= is somewhat less efficient than the + =list= and =array= commands. + + #+ATTR_TEXINFO: :tag Note + #+begin_quote + For more on sequences, see [[info:elisp#Sequences Arrays Vectors]]. This command + works with the basic sequences understood by the Emacs Lisp functions ~length~ + and ~elt~. It does not work with the generic sequences understood by the + library =seq.el=. + #+end_quote This command also has the aliases =seqing= and =sequencing=. =KEYS= is one or several of =from=, =upfrom=, =downfrom=, =to=, =upto=, =downto=, =above=, =below=, =by=, and =index=. =index= names the variable - used to store the index being accessed. For others, see the =numbers= command. + used to store the index being accessed. For others, see the =numbers= + command. If multiple sequences are given, then these keyword arguments apply to the resulting sequence of distributed elements. @@ -1872,8 +1944,9 @@ the accessed index during the loop. this command uses the =map.el= library. =key= is a variable in which to store the current key for the ~setf~-able - place referred to by =VAR=. This is similar to the =index= keyword - parameter of other commands. + place referred to by =VAR=. This is similar to the =index= keyword parameter + of other commands. This is not the same as the =key= keyword parameter of the + accumulation commands. Like in the command =map=, the keys of the map are generated before the loop is run, which can be expensive for large maps. @@ -1962,8 +2035,8 @@ Accumulation commands are used to accumulate or aggregate values into a variable, such as creating a list of values or summing the elements in a sequence. -If needed, you can refer to the same accumulation variable in multiple -accumulation commands. +Unlike iteration commands, you can refer to the same accumulation variable in +multiple accumulation commands if needed. #+begin_src emacs-lisp ;; => (1 6 2 7 3 8) @@ -1979,11 +2052,13 @@ Keep in mind that it is an error to modify accumulation variables outside of accumulation commands. This restriction allows accumulations to be much faster. #+end_quote +#+cindex: accumulation initial values Like with other loop commands, variables created by accumulation commands (such -as =coll= in the above example) are initialized to ~nil~ unless stated +as ~coll~ in the above example) are initialized to ~nil~ unless stated otherwise. When otherwise, such as for the commands =sum= and =multiply=, the initial value of a variable depends on the first accumulation command using that -variable in the arguments given to the macro. +variable in the arguments given to the macro. Remember that a variable's +initial value can be controlled using the =with= special macro argument. #+begin_src emacs-lisp ;; => 27 @@ -1993,10 +2068,19 @@ variable in the arguments given to the macro. (finally-return my-accum)) ;; => 21 - (loopy (numbers i 1 3) + (loopy (numbers i :from 1 :to 3) (multiply my-accum i) ; Starts at 1. (sum my-accum i) (finally-return my-accum)) + + ;; Using `with': + ;; + ;; => 87 + (loopy (with (my-accum 10)) + (numbers i :from 1 :to 3) + (sum my-accum i) ; Starts at 0. + (multiply my-accum i) + (finally-return my-accum)) #+end_src #+cindex: accumulation destructuring @@ -2023,7 +2107,6 @@ accumulated, instead of the destructured value. (finally-return sum1 sum2 sum3)) #+end_src - #+cindex: implied accumulation results Like in ~cl-loop~, you do not need to supply a variable name to accumulation commands. If no accumulation variable is given, then the accumulated value is @@ -2040,10 +2123,9 @@ be overridden by using the the =return= and =return-from= loop commands or the #+end_src #+vindex: loopy-result -Unlike in ~cl-loop~, this implied return value is bound to the variable -~loopy-result~ after the loop completes, even when the loop is left early. This -variable can be used in the =after-do=, =finally-do=, and =finally-return= -special macro arguments. +Unlike ~cl-loop~, Loopy uses a default accumulation variable, which is named +~loop-result~. This variable can be used in the =after-do=, =finally-do=, and +=finally-return= special macro arguments. #+begin_src emacs-lisp ;; => (0 1 2 3 4 5) @@ -2069,6 +2151,7 @@ future. #+begin_src emacs-lisp ;; See how the variable `my-explicit-variable' is ignored when ;; returning a final value: + ;; ;; => (1 2 3) (loopy (list i '(1 2 3)) (collect i) @@ -2076,32 +2159,33 @@ future. #+end_src Therefore, when mixing implicit and explicit accumulation variables, you must -use the =finally-return= special macro argument to return all of the +always use the =finally-return= special macro argument to return all of the accumulation results. #+begin_src emacs-lisp - ;; => ((1 2 3) ; loopy-result - ;; (2 4 6) ; my-other-collection - ;; (1 2 3) ; car-coll - ;; (2 4 6)) ; cdr-coll + ;; => ((1 2 3) ; `loopy-result' + ;; (2 4 6) ; `my-other-collection' + ;; (1 2 3) ; `car-coll' + ;; (2 4 6)) ; `cdr-coll' (loopy (list i '(1 2 3)) - (collect i) + (collect i) ; Uses `loopy-result' (set j (* 2 i)) (collect my-other-collection j) (collect (car-coll . cdr-coll) (cons i j)) (finally-return loopy-result my-other-collection - car-coll cdr-coll)) + car-coll + cdr-coll)) #+end_src #+cindex: accumulation compatibility Like in ~cl-loop~, when using implied variables, multiple accumulation commands will use the same variable (~loopy-result~). For _all_ accumulation variables used by multiple accumulation commands, you should make sure that the commands -are actually compatible. If not, then ~loopy~ will raise an error. +are actually compatible. If not, then ~loopy~ will signal an error. For example, you should not try to accumulate =collect= results and =sum= -results into the same variable, as one cannot use a list as a number. On the +results into the same variable, as you cannot use a list as a number. On the other hand, =sum= and =multiply= are compatible, since they both act on numbers. #+begin_src emacs-lisp @@ -2118,7 +2202,6 @@ other hand, =sum= and =multiply= are compatible, since they both act on numbers. (multiply i)) #+end_src - By default, one must specify separate accumulation variables to be able to accumulate into separate values. This can make accumulation slower, because ~loopy~ ensures that named accumulation variables (excluding the previously @@ -2134,6 +2217,7 @@ is more complex and uses a slower way of building the accumulated list. #+begin_src emacs-lisp ;; Optimized accumulation: + ;; ;; => (1 3 2 6 3 9) (loopy (accum-opt coll) (numbers i :from 1 :to 3) @@ -2142,6 +2226,8 @@ is more complex and uses a slower way of building the accumulated list. (finally-return coll)) ;; Optimized example expansion: + ;; + ;; => (1 3 2 6 3 9) (let* ((coll nil) (i 1) (nums-end192 3) @@ -2155,9 +2241,9 @@ is more complex and uses a slower way of building the accumulated list. coll) #+end_src - #+begin_src emacs-lisp ;; Unoptimized accumulation: + ;; ;; => (1 3 2 6 3 9) (loopy (numbers i :from 1 :to 3) (collect coll i) @@ -2165,6 +2251,8 @@ is more complex and uses a slower way of building the accumulated list. (finally-return coll)) ;; Unoptimized example expansion: + ;; + ;; => (1 3 2 6 3 9) (let* ((coll nil) (coll-last-link-190 coll) (i 1) @@ -2244,7 +2332,7 @@ all described below. #+cindex: accumulation keyword test - =test= :: A function of two arguments, usually used to test for equality. Most tests default to ~equal~, like in other Emacs Lisp libraries. This is - different from ~cl-lib~, which mimics Common Lisp and prefers using ~eql~. + different from =cl-lib=, which mimics Common Lisp and prefers using ~eql~. #+cindex: accumulation keyword key - =key= :: A function of one argument, used to transform the inputs of @@ -2264,49 +2352,80 @@ knows how to expand efficiently for either case. :DESCRIPTION: Accumulating function output. :END: -Generic accumulation commands accumulate the output of functions that receive -the accumulation variable. They are very similar to updating a variable's value -using the =set= command. +Generic accumulation commands are more explicit uses of the accumulation +variable. They are very similar to updating a variable's value +using the =set= command and exist for situation not covered by the other +accumulation commands. -#+findex: set-accum -#+findex: setting-accum -- =(set-accum VAR EXPR)= :: Set the accumulation variable =VAR= to the - value of =EXPR=. +- =reduce= is like ~cl-reduce~, calling a function that receives (1) the + accumulation variable and (2) the value to accumulate, in that order. +- =accumulate= works by calling a function that receives (1) the value to + accumulate and (2) the accumulation variable, in that order. +- =set-accum= is the most generic, and works like =set= for only one value. - This command also has the alias =setting-accum=. - This command is a basic wrapper around =set= for only one value. Because this - command cannot be optimized (as it does not construct a sequence), it is safe - to access the implicit variable ~loopy-result~ in =EXPR=, so long as the - variable is not being modified by another command for which that would be - unsafe. +The commands are described in more detail below. - #+begin_src emacs-lisp - ;; => 16 - (loopy (array i [1 2 3]) - (set-accum (+ loopy-result i))) +#+findex: reduce +#+findex: reducing +- =(reduce VAR EXPR FUNC)= :: Reduce =EXPR= into =VAR= by =FUNC=, like in + ~cl-reduce~ and ~(funcall FUNC VAR EXPR)~. =FUNC= is called with =VAR= as the + first argument and =EXPR= as the second argument. This is unlike + =accumulate=, which gives =VAR= and =EXPR= to =FUNC= in the opposite order + (that is, =EXPR= first, then =VAR=). - ;; These are equivalent to the above example: + This command also has the alias =reducing=. - ;; => 16 - (loopy (array i [1 2 3]) - (set loopy-result (+ loopy-result i)) - (finally-return loopy-result)) + When =VAR= does not have an explicit starting value (given with the special + macro argument =with=), the first accumulated value is =EXPR=. The first + accumulated value is not the result of passing =VAR= and =EXPR= to =FUNC=. + Using the =with= special macro argument is similar to using ~cl-reduce~'s + =:initial-value= keyword argument. - ;; => 16 - (loopy (array i [1 2 3]) - (set-accum loopy-result (+ loopy-result i)) + #+begin_src emacs-lisp + ;; => 6 + (loopy (list i '(1 2 3)) + (reduce i #'*)) + + ;; Similar to the above: + (loopy (list i '(1 2 3)) + (set loopy-result i (* i loopy-result)) (finally-return loopy-result)) + + ;; = > 6 + (loopy (with (my-reduction 0)) + (list i '(1 2 3)) + (reduce my-reduction i #'+) + (finally-return my-reduction)) + + ;; Similar to the above: + (cl-reduce #'+ (list 1 2 3) :initial-value 0) + (seq-reduce #'+ [1 2 3] 0) + #+end_src + + This command also has the alias =callf=. It is similar to using the + function ~cl-callf~, except that the function argument is given last and + must be quoted. This alias is intended to help users remember argument + order. + + #+begin_src emacs-lisp + (loopy (with (my-reduction 0)) + (list i '(1 2 3)) + (callf my-reduction i #'+) + (finally-return my-reduction)) + + ;; Is similar to the above: + (loopy (with (my-reduction 0)) + (list i '(1 2 3)) + (do (cl-callf + my-reduction i)) + (finally-return my-reduction)) #+end_src #+findex: accumulate #+findex: accumulating -- =(accumulate|accumulating VAR EXPR FUNC)= :: Accumulate the result - of applying function =FUNC= to =EXPR= and =VAR=. =EXPR= and =VAR= are used as - the first and second arguments to =FUNC=, respectively. - - This is a generic accumulation command in case the others don't meet your - needs. It is similar in effect to using the command =set=. +- =(accumulate|accumulating VAR EXPR FUNC)= :: Accumulate the result of applying + function =FUNC= to =EXPR= and =VAR= like in ~(funcall FUNC EXPR VAR)~. =EXPR= + and =VAR= are used as the first and second arguments to =FUNC=, respectively. #+begin_src emacs-lisp ;; Call `(cons i my-accum)' @@ -2345,60 +2464,38 @@ using the =set= command. (finally-return my-accum)) #+end_src -#+findex: reduce -#+findex: reducing -- =(reduce VAR EXPR FUNC)= :: Reduce =EXPR= into =VAR= by =FUNC=, like in - ~cl-reduce~. =FUNC= is called with =VAR= as the first argument and =EXPR= as - the second argument. This is unlike =accumulate=, which gives =VAR= and - =EXPR= to =FUNC= in the opposite order (that is, =EXPR= first, then =VAR=). - - This command also has the alias =reducing=. +#+findex: set-accum +#+findex: setting-accum +- =(set-accum VAR EXPR)= :: Set the accumulation variable =VAR= to the + value of =EXPR=. - When =VAR= does not have an explicit starting value (given with the special - macro argument =with=), the first accumulated value is =EXPR=. The first - accumulated value is not the result of passing =VAR= and =EXPR= to =FUNC=. - Using the =with= special macro argument is similar to using ~cl-reduce~'s - =:initial-value= keyword argument. + This command also has the alias =setting-accum=. - This command is similar in effect to the =set= command. + This command is a basic wrapper around =set= for only one value. Because this + command cannot be optimized (as it does not construct a sequence), it is safe + to access the implicit variable ~loopy-result~ in =EXPR=, so long as the + variable is not being modified by another command for which that would be + unsafe. #+begin_src emacs-lisp ;; => 6 - (loopy (list i '(1 2 3)) - (reduce i #'*)) - - ;; Similar to the above: - (loopy (list i '(1 2 3)) - (set loopy-result i (* i loopy-result)) - (finally-return loopy-result)) - - ;; = > 6 - (loopy (with (my-reduction 0)) - (list i '(1 2 3)) - (reduce my-reduction i #'+) - (finally-return my-reduction)) - - ;; Similar to the above: - (cl-reduce #'+ (list 1 2 3) :initial-value 0) - (seq-reduce #'+ [1 2 3] 0) - #+end_src + (loopy (with (loopy-result 0)) + (array i [1 2 3]) + (set-accum (+ loopy-result i))) - This command also has the alias =callf=. It is similar to using the - function ~cl-callf~, except that the function argument is given last and - must be quoted. This alias is intended to help users remember argument - order. + ;; These are equivalent to the above example: - #+begin_src emacs-lisp - (loopy (with (my-reduction 0)) - (list i '(1 2 3)) - (callf my-reduction i #'+) - (finally-return my-reduction)) + ;; => 6 + (loopy (with (loopy-result 0)) + (array i [1 2 3]) + (set loopy-result (+ loopy-result i)) + (finally-return loopy-result)) - ;; Is similar to the above: - (loopy (with (my-reduction 0)) - (list i '(1 2 3)) - (do (cl-callf + my-reduction i)) - (finally-return my-reduction)) + ;; => 6 + (loopy (with (loopy-result 0)) + (array i [1 2 3]) + (set-accum loopy-result (+ loopy-result i)) + (finally-return loopy-result)) #+end_src *** Numeric Accumulation @@ -2497,29 +2594,18 @@ Sequence accumulation commands are used to join lists (such as =union= and #+findex: adjoin #+findex: adjoining -- =(adjoin VAR EXPR &key at test key)= :: Repeatedly add =EXPR= to =VAR= if it +- =(adjoin VAR EXPR &key at test)= :: Repeatedly add =EXPR= to =VAR= if it is not already present in the list. This command also has the alias =adjoining=. - #+begin_src emacs-lisp - ;; Without a test, defaults to `eql' as in `cl-adjoin'. - ;; => ((1 . 1) (1 . 2) (1 . 2) (2 . 3)) - (loopy (list i '((1 . 1) (1 . 2) (1 . 2) (2 . 3))) - (adjoin i)) + Unlike ~cl-adjoin~ and like the other accumulation commands, this command + defaults to adjoining =EXPR= to the end of =VAR=, not the beginning. - ;; Using `equal' for the test. + #+begin_src emacs-lisp ;; => ((1 . 1) (1 . 2) (2 . 3)) (loopy (list i '((1 . 1) (1 . 2) (1 . 2) (2 . 3))) - (adjoin i :test #'equal)) - - ;; Using `=' for the test and `car' for the key. This - ;; treats '(1 . 2) as equivalent to '(1 . 1), so it - ;; won't be added. - ;; - ;; => ((1 . 1) (2 . 3)) - (loopy (list i '((1 . 1) (1 . 2) (1 . 2) (2 . 3))) - (adjoin i :test #'= :key #'car)) + (adjoin i)) ;; Coerced to a vector /after/ the loop ends. ;; => [1 2 3 4] @@ -2575,7 +2661,8 @@ Sequence accumulation commands are used to join lists (such as =union= and ;; => [1 2 3] (loopy (list j '(1 2 3)) - (collect j :result-type 'vector)) + (collect j) + (finally-return (cl-coerce loopy-result 'vector))) ;; => (3 2 1) (loopy (list j '(1 2 3)) @@ -2589,13 +2676,11 @@ Sequence accumulation commands are used to join lists (such as =union= and #+findex: concat #+findex: concating - =(concat VAR EXPR &key at)= :: Repeatedly ~concat~ the value of =EXPR= onto - =VAR=, as a string. For concatenating values onto a vector, see the command + =VAR=, as a string. For concatenating values into a vector, see the command =vconcat=. This command also has the alias =concating=. - =VAR= is a string throughout the loop. - #+BEGIN_SRC emacs-lisp ;; => "abc" (loopy (list i '("a" "b" "c")) @@ -2638,7 +2723,7 @@ Sequence accumulation commands are used to join lists (such as =union= and #+findex: nunion #+findex: nunioning -- =(nunion VAR EXPR &key test key at)= :: Repeatedly and /destructively/ insert +- =(nunion VAR EXPR &key test at)= :: Repeatedly and /destructively/ insert into =VAR= the elements of =EXPR= which are not already present in =VAR=. This command also has the alias =nunioning=. @@ -2649,11 +2734,6 @@ Sequence accumulation commands are used to join lists (such as =union= and (nunion var i) (finally-return var)) - ;; => ((a . 2)) - (loopy (array i [((a . 1)) ((a . 2))]) - (nunioning var i :key #'car) - (finally-return var)) - ;; => (4 2 (1 1) 3) (loopy (list i '(((1 1) 2) ((1 1) 3) (3 4))) (nunioning var i :test #'equal) @@ -2664,10 +2744,6 @@ Sequence accumulation commands are used to join lists (such as =union= and ((1 2 3) (3 4))]) (nunion (var1 var2) i :test #'equal) (finally-return var1 var2)) - - ;; => ((4 2) (1 2) (3 2)) - (loopy (list i '(((1 2) (3 2)) ((1 1) (4 2)))) - (nunion i :at start :key #'car)) #+end_src #+findex: prepend @@ -2716,7 +2792,7 @@ Sequence accumulation commands are used to join lists (such as =union= and #+findex: union #+findex: unioning -- =(union VAR EXPR &key test key at)= :: Repeatedly insert into =VAR= the +- =(union VAR EXPR &key test at)= :: Repeatedly insert into =VAR= the elements of the list =EXPR= that are not already present in =VAR=. This command also has the alias =unioning=. @@ -2727,11 +2803,6 @@ Sequence accumulation commands are used to join lists (such as =union= and (union var i) (finally-return var)) - ;; => ((a . 2)) - (loopy (array i [((a . 1)) ((a . 2))]) - (unioning var i :key #'car) - (finally-return var)) - ;; => (4 2 (1 1) 3) (loopy (list i '(((1 1) 2) ((1 1) 3) (3 4))) (unioning var i :test #'equal) @@ -2742,23 +2813,16 @@ Sequence accumulation commands are used to join lists (such as =union= and ((1 2 3) (3 4))]) (union (var1 var2) i :test #'=) (finally-return var1 var2)) - - ;; => ((4 2) (1 2) (3 2)) - (loopy (list i '(((1 2) (3 2)) ((1 1) (4 2)))) - (union var i :at 'start :key #'car) - (finally-return var)) #+end_src #+findex: vconcat #+findex: vconcating -- =(vconcat VAR EXPR)= :: Repeatedly concatenate the value of =EXPR= onto =VAR= - via the function ~vconcat~. For concatenating values onto a string, see the - command =concat=. +- =(vconcat VAR EXPR &key at)= :: Repeatedly concatenate the value of =EXPR= + onto =VAR= via the function ~vconcat~. For concatenating values into a + string, see the command =concat=. This command also has the alias =vconcating=. - =VAR= is a vector throughout the loop. - #+BEGIN_SRC emacs-lisp ;; => [1 2 3 4 5 6] (loopy (list i '([1 2 3] [4 5 6])) @@ -2774,86 +2838,39 @@ Sequence accumulation commands are used to join lists (such as =union= and #+findex: find #+findex: finding -- =(find VAR EXPR TEST &key ON-FAILURE)= :: If =TEST= is non-nil, the loop stops - and =EXPR= is used as a returned value. If =TEST= is never non-nil, then - =ON-FAILURE= is used as a returned value, if provided. +- =(find VAR EXPR TEST &key ON-FAILURE)= :: If the expression =TEST= is non-nil, + then the loop stops and =VAR= is set to the value of =EXPR=. If =TEST= is + never non-nil, then =VAR= is set to the value of =ON-FAILURE=, if provided. This command also has the alias =finding=. - =VAR= takes the value of =EXPR= if =TEST= is non-nil or =ON-FAILURE= if the - loop completes successfully. It is bound to ~nil~ during the loop. As with - other accumulation commands, if =VAR= is provided, then =EXPR= is not used as - a return value. Instead, it is assigned to =VAR=, which must be returned - explicitly. + If the loop is left early and =TEST= was never non-nil, this is the same as a + normal failure and =VAR= will be set to the value of =ON-FAILURE=, if + provided. #+BEGIN_SRC emacs-lisp - ;; => 3 - (loopy (list i '(1 2 3)) - (finding i (> i 2))) - - ;; Equivalent to above. - (loopy (list i '(1 2 3)) - (when (> i 2) (return i))) + ;; => (13 (1 2)) + (loopy (list i '(1 2 3 4 5 6 7 8)) + (find (+ i 10) (> i 2)) + (collect coll i) + (finally-return loopy-result coll)) ;; => nil - (loopy (list i '(1 2 3)) - (finding i (> i 4))) + (loopy (list i '(1 2 3 4 5 6)) + (find i (> i 12))) - ;; Equivalent to above. - (loopy (list i '(1 2 3)) - (when (> i 4) (return i))) - - ;; => "not found" - (loopy (list i '(1 2 3)) - (finding i (> i 4) :on-failure "not found")) - - ;; Equivalent to above. - (loopy (list i '(1 2 3)) - (when (> i 4) (return i)) - (else-do (cl-return "not found"))) + ;; => 27 + (loopy (list i '(1 2 3 4 5 6)) + (find i (> i 12) :on-failure 27)) - ;; Does not display message. - ;; => 2 - (loopy (list i '(1 2 3)) - (finding i (= i 2) :into found) - (after-do (message "found: %s" found)) - (finally-return found)) + ;; => 27 + (loopy (list i '(1 2 3 4 5 6)) + (while (< i 3)) + (find i (> i 12) :on-failure 27)) - ;; Equivalent to above. - (loopy (list i '(1 2 3)) - (when (= i 2) - (set found i) - (leave)) - (after-do (message "found: %s" found)) - (finally-return found)) - - ;; Messages "found: 2" in echo area. - ;; => 2 - (loopy (list i '(1 2 3)) - (finding found i (= i 2)) - (finally-do (message "found: %s" found)) - (finally-return found)) - - ;; Equivalent to above. - (loopy (list i '(1 2 3)) - (when (= i 2) - (set found i) - (leave)) - (finally-do (message "found: %s" found)) - (finally-return found)) - - ;; => "not found" - (loopy (list i '(1 2 3)) - (finding whether-found i (> i 4) :on-failure "not found") - (finally-return whether-found)) - - ;; Equivalent to above. - (loopy (list i '(1 2 3)) - (when (> i 4) - (set whether-found i) - (leave)) - (else-do (setq whether-found "not found")) - (finally-return whether-found)) + ;; => nil + (loopy (list i '(1 2 3 4 5 6)) + (find nil (> i 3) :on-failure 27)) #+END_SRC *** Optimizing Accumulations @@ -2896,8 +2913,8 @@ order, so commands using such variables can produce more efficient code. #+end_src The situation becomes more complex when commands place values at both sides of a -sequence. In that case, ~loopy~ keeps track of the beginning /and/ the end -of the sequence. ~loopy~ does /not/ merely append to the end of the resulting +sequence. In that case, ~loopy~ keeps track of the beginning /and/ the end of +the sequence. ~loopy~ /does not/ merely append to the end of the accumulating list, since that would be much slower for large lists. #+begin_src emacs-lisp @@ -2951,6 +2968,7 @@ In the example below, see that #+begin_src emacs-lisp ;; This code optimizes for insertions at the end of `coll': + ;; ;; => ((23 13 22 12 21 11 1 2 3) ;; ((1 11 21) (2 1 11 21 12 22) (3 2 1 11 21 12 22 13 23))) (loopy (accum-opt (coll end)) @@ -2964,8 +2982,8 @@ In the example below, see that The =accump-opt= special macro argument can also be used with destructuring. Because destructuring requires using named variables, such variables are by -default required to be ordered correctly during the loop. If you do not require -that, you are recommended to use =accum-opt= on those variables. +default required to be ordered correctly during the loop. If you do not need +them to be so, you are recommended to use =accum-opt= on those variables. #+begin_src emacs-lisp ;; => ((1 3) @@ -2988,38 +3006,30 @@ that, you are recommended to use =accum-opt= on those variables. {{{dfn(Boolean commands)}}} are used to test whether a condition holds true during the loop. They work like a combination of iteration and accumulation -commands, in that values are stored in ~loopy-result~ and that can terminate the -loop. +commands, in that values are stored in ~loopy-result~ and that they can +terminate the loop. -The behavior and use of the boolean commands is a compromise between consistency -with other commands, similarity to how similar features are used in other -libraries, and convenience for how they are commonly used. This gives us the -following: +Be aware of the following: -- ~loopy-result~ is used as the implicit return value of the loop. +- Currently, unlike accumulation commands, there is no non-keyword way to + specify a variable. The first argument (the only required argument) of each + boolean command is a condition to check. - Like accumulation commands, the keyword =:into= can be used the specify a variable other than ~loopy-result~. - - Unlike accumulation commands, there is no non-keyword way to specify a - variable. The first argument (the only required argument) of each boolean - command is a condition to check. - - - The =always= and =never= commands must use the same variable to work - together correctly. By default, the both use ~loopy-result~. +- The =always= and =never= commands must use the same variable to work + together correctly. By default, the both use ~loopy-result~. -- These commands exit the loop without forcing a value ([[#exiting-the-loop-early]]). - - - Therefore, optimized accumulation variables can be finalized even when the - loop ends, as happens with the =leave= command. - - - However, because the boolean commands already use ~loopy-result~, such - optimized accumulation variables must be created with the special macro - argument =accum-opt= and must be used explicitly, as in the below example. +- These commands exit the loop without forcing a return value + ([[#exiting-the-loop-early]]). Therefore, optimized accumulation variables can be + finalized even when the loop ends, as happens with the =leave= command. + However, because the boolean commands already use ~loopy-result~, such + optimized accumulation variables must be created with the special macro + argument =accum-opt= and must be used explicitly, as in the below example. #+begin_src emacs-lisp - ;; A maybe unidiomatic example: ;; => (nil (1 3 5)) (loopy (accum-opt coll) (list i '(1 3 5 6 9)) @@ -3027,19 +3037,10 @@ following: (collect coll i) (finally-return loopy-result coll)) - ;; Same as above, but maybe more idiomatic: - ;; => (nil (1 3 5)) - (loopy (with (succes t)) - (list i '(1 3 5 6 9)) - (if (cl-oddp i) - (collect i) - (set success nil) - (leave)) - (finally-return success loopy-result)) - ;; Works similarly, but forces the `nil' return value. ;; Returns the collection if `always' doesn't trigger an exit. ;; Attempting similar with CL's `iterate' will signal an error. + ;; ;; => nil (cl-loop for i in '(1 3 5 6 9) always (cl-oddp i) @@ -3048,11 +3049,12 @@ following: #+attr_texinfo: :tag Warn #+begin_quote -Using the command =thereis= is incompatible with using the commands =always= and -=never=, as this would create conflicting initial values for the implicit return -value (both using ~loopy-result~). +Using the command =thereis= for an variable incompatible with using the commands +=always= and =never= on that same variable, as this would create conflicting +initial values for the implicit return value (both using ~loopy-result~). #+end_quote + #+findex: always - =(always EXPR &key into)= :: Check the result of the condition =EXPR=. If the condition evaluates to ~nil~, end the loop. Otherwise, the loop returns the @@ -3133,6 +3135,10 @@ value (both using ~loopy-result~). (loopy (cycle 2) (always 2) (never nil)) + ;; => 2 + (loopy (cycle 2) + (never nil) + (always 2)) #+end_src #+findex: thereis