Next: Introduction [Contents][Index]
loopy
is a macro meant for iterating and looping. It is similar in usage to
cl-loop
((cl)Loop Facility) but uses parenthesized expressions rather than
keyword clauses.
For most cases, loopy
is a featureful replacement for cl-loop
and
complementary to Emacs’s built-in looping and mapping features (such as the
libraries ‘seq’ ((elisp)Sequence Functions) and ‘cl-lib’ ((cl)Top)).
loopy-iter
Macro
cl-loop
Next: Basic Concepts, Previous: Loopy: A Looping and Iteration Macro, Up: Loopy: A Looping and Iteration Macro [Contents][Index]
Loopy is a library for looping and iteration, with supplemental features for 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
parenthetical forms instead of keyword “clauses”.
;; A simple usage of `cl-loop': (cl-loop for i from 1 to 10 if (cl-evenp i) collect i into evens else collect i into odds end ; This `end' keyword is optional here. finally return (list odds evens)) ;; How it could be done using `loopy': (loopy (numbers i :from 1 :to 10) (if (cl-evenp i) (collect evens i) (collect odds i)) (finally-return odds evens))
loopy-iter
A macro similar to Common Lisp’s Iterate macro (The loopy-iter
Macro).
Unlike Common Lisp’s loop
, the iterate
macro allows the embedding of its
looping constructs inside of arbitrary code. To be clear, loopy-iter
is
not a port of iterate
to Emacs Lisp.
;; => ((1 2 3) (-3 -2 -1) (0)) (loopy-iter (numbering i -3 3) (pcase i ((pred cl-plusp) (collecting positives i)) ((pred cl-minusp) (collecting negatives i)) (_ (collecting other i))) (finally-return positives negatives other))
loopy-let*
, loopy-setq
, loopy-lambda
, and loopy-ref
Destructuring
macros that can be used outside of loopy
and loopy-iter
(Basic Destructuring). For convenience, Loopy provides its own form of
destructuring, which is similar to, though more featureful than, that provided
by ‘cl-lib’.
;; => (1 2 3 (:k1 4) 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]) (let ((my-list (list 1 2 3)) (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) (list my-list my-vector)))
Some other things to note are:
loopy
(and so loopy-iter
) supports destructuring for both iteration and
accumulation commands.
;; Summing the nth elements of arrays: ;; => (8 10 12 14 16 18) (loopy (list (list-elem1 list-elem2) '(([1 2 3] [4 5 6]) ([7 8 9] [10 11 12]))) (sum [sum1 sum2 sum3] list-elem1) (sum [sum4 sum5 sum6] list-elem2) (finally-return sum1 sum2 sum3 sum4 sum5 sum6)) ;; Separate the elements of sub-list: ;; => ((1 3) (2 4)) (loopy (list i '((1 2) (3 4))) (collect (elem1 elem2) i) (finally-return elem1 elem2))
;; Use `pcase' to destructure array elements: ;; => ((1 2 3 4) (10 12 14) (11 13 15)) (loopy (flag pcase) (array (or `(,car . ,cdr) digit) [1 (10 . 11) 2 (12 . 13) 3 4 (14 . 15)]) (if digit (collect digits digit) (collect cars car) (collect cdrs cdr)) (finally-return digits cars cdrs))
cl-loop
, more constructs are provided for how loops are
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.
;; => (1 3 5) (loopy (numbers i :from 1 :to 10) (cond ((cl-evenp i) (skip)) ((> i 5) (leave))) (collect i)) ;; => (t nil) (loopy (with (always-run) (run-when-complete)) (numbers i :from 1 :to 10) (while (< i 4)) (after-do (setq run-when-complete t)) (finally-do (setq always-run t)) (finally-return always-run run-when-complete))
;; 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))
All that being said, Loopy is not yet feature complete. Please request features or report problems in this project’s issues tracker.
Next: Special Macro Arguments, Previous: Introduction, Up: Loopy: A Looping and Iteration Macro [Contents][Index]
Except for an optional loop name, all arguments of the loopy
macro are
parenthesized expressions. These expressions can, for example, assign variables
local to the loop, add code that runs before/after the loop, and/or set the
ultimate return value of the macro.
For convenience and clarity, expressions that generate code in the loop body are called “loop commands” (Loop Commands). Expressions that generate code around the loop are called “special macro arguments” or just “macro arguments” as opposed to “loop commands” (Special Macro Arguments).
“Loop commands” are the main building blocks of the loopy
macro, such as the
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’ 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
until a early-exit command is reached (Exiting the Loop Early).
The default return value of the loop is nil
. Other return values must be
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).
The loopy
macro is configurable. One can add custom commands (Custom
Commands), add custom command aliases (Custom Aliases), and specify macro
options for a particular loop (Using Flags). Each of these features is
explained in detail later in this document.
Next: Loop Commands, Previous: Basic Concepts, Up: Loopy: A Looping and Iteration Macro [Contents][Index]
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 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
.
Name the loop. This also names the cl-block
which contains the loop. This can be of the form ‘(named NAME)’ or just
‘NAME’.
;; => 3 (loopy outer (array i [(1 2) (3 4) (5 6)]) (loopy (list j i) (when (> j 2) (return-from outer j)))) ;; => 3 (loopy (named outer) (array i [(1 2) (3 4) (5 6)]) (loopy (list j i) (when (> j 2) (return-from outer j))))
Declare variables before the loop, in order. This can also be used to initialize variables referenced by loop commands. ‘with’ can use destructuring (Basic Destructuring).
;; => (4 5 6) (loopy (with (a 1) ; Set `a' to 1. (b (1+ a))) ; Set `b' to 1+1=2. (list i '(1 2 3)) ; Bind `i' to elements of the list. (collect (+ i a b))) ; Collect sum of `a', `b', and each `i' into a list. ;; => 16 (loopy (let* (my-sum 10)) ; Bind `my-sum' to 10. (list i '(1 2 3)) ; Bind `i' to elements of the list. (sum my-sum i) ; Set `my-sum' to `i' + `my-sum'. (finally-return my-sum)) ; Return the value of `my-sum'.
Variables that loopy
should not try to
initialize. loopy
tries to initialize all of the variables that it uses
in a let
-like form, but that isn’t always desired.
;; 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))) (collect a) (set a (1- a))))
Run Lisp expressions before the loop starts, after variables are initialized.
;; => (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'. (list i '(1 2 3)) ; Set `i' to each element in the list. (collect (+ i a b))) ; Collect each sum into a list. ;; => (1 2 3) (loopy (with (a 1)) ;; Message before the loop starts: (initially (message "Starting loop...")) (list i '(1 2 3)) (collect i))
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.
;; Messages that no odd number was found: ;; => nil (loopy (list i '(2 4 6 8)) (when (cl-oddp i) (do (message "Odd number found.")) (return t)) ; Make the loop return `t'. (after-do (message "No odd number found.") ;; The macro already return `nil' by default, ;; but one can still use `cl-return' to be more explicit. (cl-return nil))) ;; Messages that an odd number was found: ;; => t (loopy (list i '(2 4 5 8)) (when (cl-oddp i) (do (message "Odd number found.")) (return t)) (else (message "No odd number found.")))
Run Lisp expressions after the loop exits, always.
Unlike progn
, the return values of the expressions do not affect the
return value of the macro.
;; => (nil finally) (let (a b) (loopy (list i '(1 2 3 4 5 6)) (when (> i 3) (leave)) (after-do (setq a 'after)) (finally-do (setq b 'finally))) (list a b)) ;; => nil (loopy (leave) ;; Doesn't affect return value: (finally-do 999))
Return a value, regardless of how the loop completes. These arguments override any explicit return values given in commands like ‘return’ and ‘return-from’, as well as any implicit return values that can be created by accumulation commands.
Specifying multiple values is the same as returning a list of those values.
;; => 999 (loopy (return 1) (finally-return 999)) ;; => (1 2) (loopy (leave) ; Leave to avoid infinite loop. (finally-return 1 2))
Wrap the loop in unwind-protect
(not to be confused with condition-case
). The arguments to this special
macro argument (which are Lisp expressions) can access the variables used by
the loop.
Signaling an error will prevent the loop from returning a value. This special macro argument does not prevent that error from being signaled, and is only meant to help avoid lingering effects that might arise from unplanned stops of the loop’s execution.
;; Prints out the following, then continues signalling the error: ;; ;; Example var is: 1 ;; Last used element in list is: 4 ;; Then current value of ‘my-collection’: (1 2 3 4) (loopy (with (example-var 1)) (list i '(1 2 3 4 5)) (collect my-collection i) (when (> i 3) (do (signal 'some-error (list i)))) (finally-protect (message "Example var is: %d" example-var) (message "Last used element in list is: %s" i) (message "Then current value of `my-collection': %s" my-collection)))
Options that change the behavior of loopy
(Using Flags).
For example, one can opt to use a different destructuring system, such as
what is provided by the Dash library. See that linked section for more
information.
;; Use Dash for destructuring: ;; ;; (((1 2) (3 4)) (1 3) (2 4)) (require 'loopy-dash) (loopy (flag dash) (list (whole &as a b) '((1 2) (3 4))) (collect wholes whole) (collect as a) (collect bs b) (finally-return wholes as bs)) ;; Use Seq for destructuring: ;; ;; => (1 [2 3]) (loopy (flag seq) (with ([a &rest b] [1 2 3])) (return a b))
Accumulation variables whose use should be optimized (Optimizing Accumulations). Implicit accumulation variables are always optimized, but explicit variables are unoptimized by default (Accumulation). This special macro argument allows optimizing named variables (with restrictions), which is useful when using more than one accumulation variable. This is especially important for destructuring accumulations.
;; Multiple accumulation variables in `cl-loop': ;; => ((2 4) (1 3)) (cl-loop for i in '(1 2 3 4) if (cl-evenp i) collect i into evens else collect i into odds finally return (list evens odds)) ;; Faster code than above `cl-loop' (try `pp-macroexpand-last-sexp'): ;; => ((2 4) (1 3)) (loopy (accum-opt evens odds) (list i '(1 2 3 4)) (if (cl-evenp i) (collect evens i) (collect odds i)) (finally-return evens odds))
A list of forms in which to wrap the loop itself (that is, not
‘before-do’, ‘after-do’, or anything else). Each form can be either a list
or a symbol. If a list, the loop is inserted into the end of the list. If
a symbol, it is first converted to a list of 1 element before inserting the
loop at the end of the list. This special macro argument is similar in use
to the Emacs Lisp macro thread-last
, except that forms listed first are
applied last, as in normal Lisp code.
The main difference between using this macro argument instead of just writing the function calls normally is that these forms can access variables initialized by the macro and that they occur after the code in ‘before-do’ is run.
(loopy (wrap (let ((a 1))) save-match-data) ...) ;; Similar to (let ((a 1)) (save-match-data (loopy ...))) ;; => 6 (loopy (with (a 1)) (before-do (cl-incf a 2)) (wrap (progn (setq a (* 2 a)))) (return a))
As stated above, all other expressions will be considered loop commands (Loop Commands).
Note: For convenience, the
while
-loop thatloopy
creates is wrapped by acl-block
. Naming the loop names this block, which is created after initializing variables.The two special macro arguments ‘before-do’ and ‘after-do’ (and their aliases) also occur within this
cl-block
, before and after the loop, respectively. This has 2 consequences:
- Using
cl-return
in ‘before-do’ will prevent the both loop and ‘after-do’ code from running.- Using
cl-return
or an early exit command (Early Exit) in the loop will prevent the ‘after-do’ code from running. For this reason, ‘after-do’ is run if and only if the loop completes successfully, hence the alias ‘else-do’ and the similarity to Python’selse
statement when used with loops.These three sections (‘before-do’, ‘after-do’, and the
while
-loop itself) are the only structures that occur within thecl-block
. Usingcl-return
in ‘before-do’, for example, will not stop code in ‘finally-do’ from running or values listed in ‘finally-return’ from being returned.
Next: Destructuring Macros, Previous: Special Macro Arguments, Up: Loopy: A Looping and Iteration Macro [Contents][Index]
If a macro argument does not match one of the previously listed special macro
arguments (Special Macro Arguments), loopy
will attempt to treat it as a loop
command. Loop commands are only valid as a top-level argument to the macro, or
inside another loop command.
Therefore, these macro calls are valid:
(loopy (list i '(1 2 3)) (collect coll i) ;; Special macro argument: (finally-return coll)) ;; Implicit accumulation variable and implicit return value: (loopy (list i '(1 2 3)) (collect i))
and this is not:
(loopy (with (list i '(1 2 3))) (finally-return (collect coll i)))
Trying to use loop commands in places where they don’t belong will result in 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 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’.
;; => (nil 1 2) (loopy (collect coll i) (list i '(1 2 3)) (finally-return coll))
Listing 4.1: An example of how loop commands are evaluated in order.
For convenience and understanding, the same command might have multiple names,
called aliases. Similary, the ‘array’ command has the alias
‘string’, because the ‘array’ command can be used to iterate through the
elements of an array or string1. You can define custom aliases using the
macro loopy-defalias
(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.
Command | “-ing” Alias |
---|---|
‘set’ | ‘setting’ |
‘list’ | ‘listing’ |
‘collect’ | ‘collecting’ |
‘numbers’ | ‘numbering’ |
Some commands take optional keyword arguments. For example, the command ‘list’ can take a function argument following the keyword ‘:by’, which affects how that command iterates through the elements in the list.
For simplicity, the commands are described using the following notation:
#'my-func
or 'my-func
, a
variable whose value is a function, or a lambda
expression.
Generally, ‘VAR’ is initialized to nil
, but not always. This document tries
to note when that is not the case. For when that is not the case, the variable
can still be initialized to nil
if it is set to nil
using the ‘with’ special
macro argument. These special cases allow for more efficient code and less
indirection.
;; => (0 1 2 3) (loopy (collect i) (numbers i :from 0 :to 3)) ;; => (nil 0 1 2) (loopy (with (i nil)) (collect i) (numbers i :from 0 :to 3))
Unlike cl-loop
in some cases, in Loopy, the values passed as keyword arguments
are evaluated only once. For example, the command ‘(list i some-list :by
(get-function))’ evaluates (get-function)
only once. It does not evaluate it
repeatedly for each step of the loop.
;; Passes the assertion: ;; ;; => (0 1 2 3 4 5 6 7 8 9 10) (loopy (with (times 0)) (list i (number-sequence 0 10) :by (progn (cl-assert (= times 0)) (cl-incf times) #'cdr)) (collect i)) ;; => Fails the assertion on the second step of the loop: (cl-loop with times = 0 for i in (number-sequence 0 10) by (progn (cl-assert (= times 0)) (cl-incf times) #'cdr) collect i)
Next: Generic Evaluation, Up: Loop Commands [Contents][Index]
Similar to features like seq-let
, cl-destructuring-bind
, and pcase-let
,
loopy
is capable of destructuring values when assigning values to variables.
Destructuring in Loopy is similar to, but more featureful than, what is
provided in ‘cl-lib’.
Some differences include:
setf
-able places in a sequence
(Sequence Reference Iteration)
... &optional (var default) ...
) can be specified using
square brackets as well as parentheses (such as ... &optional [var default]
...
). Since such variables can be further destructured by being written as
sequences themselves, allowing both parentheses and brackets reduces confusion
and improves consistency.
map-elt
instead of
plist-get
and which does not error when the map contains keys which aren’t
matched (in other words, there is no need for an equivalent of
‘&allow-other-keys’).
This section describes the basic built-in destructuring used by most loop commands, such as ‘set’ and ‘list’. Destructuring in accumulation commands (Accumulation) and sequence-reference commands (Sequence Reference Iteration) works slightly differently, and is described more in those sections.
In addition to what can be done in loop commands, several features are available
for using Loopy’s destructuring outside of loopy
loops (Destructuring Macros),
including the pcase
pattern ‘loopy’.
The last thing to note is that loopy
loops can be made to use alternative
destructuring systems, such as seq-let
or pcase-let
. This is done by using
the ‘flag’ special macro argument (Using Flags). If you are familiar with the
package ‘dash’ 2 and its Clojure-style destructuring, consider trying
the flag ‘dash’ provided by the package ‘loopy-dash’.
Below are two examples of destructuring in cl-loop
and loopy
.
;; => (1 2 3 4) (cl-loop for (i . j) in '((1 . 2) (3 . 4)) collect i collect j) ;; => (1 2 3 4) (loopy (list (i . j) '((1 . 2) (3 . 4))) (collect i) (collect j))
Listing 4.2: Destructuring values in a list.
;; => (1 2 3 4) (cl-loop for elem in '((1 . 2) (3 . 4)) for (i . j) = elem collect i collect j) ;; => (1 2 3 4) (loopy (list elem '((1 . 2) (3 . 4))) (set (i . j) elem) (collect i) (collect j))
Listing 4.3: Destructuring values in assignment.
You can use destructured assignment by passing an unquoted sequence of symbols as the ‘VAR’ argument of a loop command. Loopy supports destructuring lists and arrays (which includes strings and vectors).
seq-elt
and
seq-drop
), use a vector or a list whose first element is ‘&seq’, as in
‘[&seq a b c]’ and ‘(&seq a b c)’.
This sequence of symbols can be shorter than the destructured sequence, but not longer. If shorter, the unassigned elements of the destructured sequence are simply ignored.
The content of this destructuring sequence is similar to ‘cl-lib’, and is
POSITIONAL-VARIABLES &optional OPTIONAL-VARIABLES &rest REST-VARIABLE &key KEY-VARIABLES [&allow-other-keys] &map MAP-VARIABLES &aux AUXILLIARY-VARIABLES
in which at least one of the above constructs must be provided.
;; => (1 2 3 ;; 4 5 t ;; (:k1 111 :k2 222) ;; 111 t ;; 222 ;; 111 ;; 333 nil ;; 4444 5555) (pcase (list 1 2 3 4 5 :k1 111 :k2 222) ((loopy ( a b c &optional d (e nil e-supplied) &rest r &key ((:k1 k1) nil k1-supplied) k2 &map (:k1 map1) [:k3 map3 333 map3-supplied] &aux [x1 4444] (x2 5555))) (list a b c d e e-supplied r k1 k1-supplied k2 map1 map3 map3-supplied x1 x2)))
In more detail, the elements of the destructuring sequence can be:
;; ((1 2 3) (4 5 6)) (loopy (list [i (j k)] '([1 (2 3)] [4 (5 6)])) (collect (list i j k)))
;; 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)) ;; => (1 3) (loopy (array (a . _ignored) [(1 2) (3 4)]) (collect a))
This is the same as when used in a CL lambda
list.
;; 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))) (collect (list both i j))) (mapcar (cl-function (lambda ((&whole both i j)) (list both i j))) '((1 2) (3 4)))
lambda
list. These variables can themselves be sequences to be further
destructured.
When used after optional values, the ‘&rest’ value is the subsequence starting at the index after any possible optional values, even when those optional values are not actually present. If the sequence is not long enough, then the sub-sequence is empty.
;; => (1 2 (3)) (pcase (list 1 2 3) ((loopy (a &optional b &rest c)) (list a b c))) ;; => (1 nil nil) (pcase (list 1) ((loopy (a &optional b &rest c)) (list a b c))) ;; => (1 []) (pcase (vector 1) ((loopy [a &optional _ _ _ _ &rest c]) (list a c)))
This ‘&rest’ is the same as when used in seq-let
.
;; => ((1 [2 3]) (4 [5 6])) (loopy (list [i &rest j] '([1 2 3] [4 5 6])) (collect (list i j))) ;; => ((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 &rest j) '((1 2 3) (4 5 6))) (collect (list i j))) ;; => ((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)))
nil
or, if specified, a
default value. Additionally, one may bind a variable to record whether the
sequence was long enough to contain the optional value.
As in a CL lambda
list, the variable has the one of the following forms:
;; => (1 2 88 t nil) (loopy (array (a &optional ((b &optional (c 88 c-supplied)) (list 77) bc-supplied)) [(1 (2))]) (collect (list a b c bc-supplied c-supplied))) ;; => (1 2 3 t t) (loopy (array (a &optional ((b &optional (c 88 c-supplied)) (list 77) bc-supplied)) [(1 (2 3))]) (collect (list a b c bc-supplied c-supplied)))
‘&optional’ cannot be used after ‘&rest’.
;; => ((1 2 3 4 5) ;; 1 ;; 2 ;; 3 ;; (4 5)) (loopy (array (&whole all a b &optional c &rest d) [(1 2 3 4 5)]) (collect (list all a b c d))) ;; Same as above: (loopy (array (&whole all a b &rest (c &rest d)) [(1 2 3 4 5)]) (collect (list all a b c d)))
plist-get
, which returns nil
if the key isn’t found in the list.
Only lists support this destructuring.
;; => ((1 2 nil) (4 5 nil)) (loopy (list (&key a b missing) '((:b 2 :c 3 :a 1) (:a 4 :b 5 :c 6))) (collect (list a b missing)))
Variables after ‘&key’ can be of the following forms:
If a default value is provided, then keys are sought using plist-member
.
That way, a value of nil
for a key is not the same as a missing key.
;; 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))) (collect (list a b c missing)))
By default, the sought key is made by prepending a colon (“:”) to the symbol name. For example, ‘a’ searches for ‘:a’ and ‘b’ searches for ‘:b’. Like in ‘cl-lib’, an evaluated key can be sought by using a sub-sequence as the first element of the list. When ‘VAR’ is a sequence, the key must be provided separately.
;; => ((1 nil t)) (loopy (list (&key ((:cat c)) ((:dog d) 27 dog-found)) '((:cat 1 :dog nil))) (collect (list c d dog-found)))
Keys are sought in values after those bound to positional variables, which can be the same values bound to the variable named by ‘&rest’ when both are used.
;; 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)))
‘&key’ and ‘&rest’ can be used in any order, but ‘&key’ must come before the dot in dotted lists.
;; => ((1 (:k1 3) 3)) (loopy (array (a &rest b &key k1) [(1 :k1 3)]) (collect (list a b k1))) (loopy (array (a &key k1 &rest b) [(1 :k1 3)]) (collect (list a b k1))) (loopy (array (a &key k1 . b) [(1 :k1 3)]) (collect (list a b k1)))
Like in ‘cl-lib’, if, after searching for the other keys, there remains an unmatched key in the destructured value, an error is signaled unless ‘&allow-other-keys’ is also used, or unless the key ‘:allow-other-keys’ is associated with a non-nil value in the property list.
;; Error due to presence of `:k3': (cl-destructuring-bind (a b &rest c &key k1 k2) (list 1 2 :k1 3 :k2 4 :k3 5) (list a b c k1 k2)) ;; Works as expected: ;; ;; => (1 2 (:k1 3 :k2 4 :k3 5) 3 4) (cl-destructuring-bind (a b &rest c &key k1 k2 &allow-other-keys) (list 1 2 :k1 3 :k2 4 :k3 5) (list a b c k1 k2))
map-let
from the library ‘map.el’. ‘&map’ works similarly to ‘&key’, but has a few
important differences:
map-elt
. The built-in maps are
arrays, property lists (“plists”), association lists (“alists”), and hash
tables. This generality means that it is slower than ‘&key’ for property
lists, though the difference should be small.
Variables after ‘&map’ can be of the following forms:
When ‘KEY’ is not given, then the key is the symbol ‘VAR’, as in (quote
VAR)
. Unlike with ‘&key’, it is not prepended with a colon.
;; => ((1 2 3 4 27)) (loopy (array (a b &map c ('dog d) (:elephant e 27)) [(1 2 c 3 dog 4)]) (collect (list a b c d e))) ;; => ((1 2 3 4 27 33 nil)) (loopy (array ( a b &map c ('dog d) (:elephant e 27) (:fox f 33 fox-found)) [(1 2 (c . 3) (dog . 4))]) (collect (list a b c d e f fox-found))) ;; => ((1 2 5 t)) (loopy (array (a b &map (:fox f 33 fox-found)) [(1 2 (c . 3) (dog . 4) (:fox . 5))]) (collect (list a b f fox-found))) ;; For arrays, the key is the index: ;; ;; => ((20 50)) (loopy (list (&map (2 two-times-ten) (5 five-times-ten)) (list [00 10 20 30 40 50 60 70 80 90 100])) (collect (list two-times-ten five-times-ten)))
When ‘&map’ and ‘&key’ are used together, they search through the same values. The use of both is normally redundant.
;; => (1 2 (:k1 3 :k2 4) ;; 3 4 ;; 3 4) (loopy (array ( a b &rest c &key ((:k1 key-k1)) ((:k2 key-k2)) &map (:k1 map-k1) (:k2 map-k2)) [(1 2 :k1 3 :k2 4)]) (collect (list a b c key-k1 key-k2 map-k1 map-k2)))
;; => (7 7 7) (loopy (cycle 3) (collect (&aux [coll 7]) 'ignored) (finally-return coll))
seq-elt
and seq-drop
. This form is less efficient than destructuring a sequence as
an array or as a list, when applicable.
Sequences destructured using ‘&seq’ can still use ‘&whole’, ‘&optional’, ‘&rest’, and ‘&map’. However, lists destructured using ‘&seq’ cannot be destructured using ‘&key’.
;; => ((0 1 2 nil nil) ;; (3 4 5 [6 7]) ;; (?a ?b ?c "")) (loopy (list [&seq i j &optional k &rest r] '((0 1) [3 4 5 6 7] "abc")) (collect (list i j k r)))
Next: Iteration, Previous: Basic Destructuring, Up: Loop Commands [Contents][Index]
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.
Evaluate multiple Lisp expressions, like a ‘progn’.
You cannot include arbitrary code in the loop body in loopy
. Trying to do
so will result in errors, as the macro will attempt to interpret such code as
a command.
To use loopy commands in arbitrary code, use the macro loopy-iter
instead
(The loopy-iter
Macro).
(loopy (list i '(1 2 3)) (do (message "%d" i)))
Evaluate multiple loop commands, as if in a ‘progn’. This is similar to ‘do’, but runs commands instead of normal Lisp expressions. Currently, this command is only useful when used within the ‘if’ command.
;; Report whether an even number is found, or return the sum of ;; the list's elements. To be clear, this is not an idiomatic example. ;; Returns the detected even number: ;; => 2 (loopy (list i '(1 3 2 5)) (if (cl-evenp i) (command-do (do (message "Even found.")) (return i)) (sum i)))
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’.
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’ (Special Macro Arguments).
;; => '(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))
Bind ‘VAR’ to a value ‘VAL’ from a previous cycle in the loop. With ‘BACK’ (default: 1), use the value from that many cycles previous. If not enough cycles have passed yet, then the value of ‘VAR’ is not modified. This command does not work like a queue for recording ‘VAL’; it always uses the value from the ‘BACK’-th previous cycle, regardless of when the command is run. The value used is always the value at the end of the cycle.
This command also has the aliases ‘setting-prev’ and, for typo tolerance, ‘prev-set’.
;; => (nil 1 2 3 4) (loopy (list i '(1 2 3 4 5)) (set-prev j i) (collect j)) ;; => (nil nil nil 1 2) (loopy (with (n 3)) (list i '(1 2 3 4 5)) (set-prev j i :back n) (collect j)) ;; NOTE: `j' isn't overwritten until the correct cycle: ;; ;; => ((first-val nil) (first-val nil) (1 2) (3 4)) (loopy (with (j 'first-val)) (list i '((1 . 2) (3 . 4) (5 . 6) (7 . 8))) (set-prev (j . k) i :back 2) (collect (list j k))) ;; NOTE: `prev-expr' keeps track of the previous value of `i', ;; even when `j' isn't updated. ;; ;; => (first-val first-val 2 2 4 4 6 6 8 8) (loopy (with (j 'first-val)) (numbers i :from 1 :to 10) (when (cl-oddp i) (set-prev j i)) (collect j)) ;; NOTE: `j' is always bound to the previous value of `i' ;; from the end of the specified cycle. ;; ;; => (nil 101 102 103) (loopy (numbers i :from 1 :to 4) (set i2 i) (set-prev j i2) (set i2 (+ i 100)) (collect j))
Next: Accumulation, Previous: Generic Evaluation, Up: Loop Commands [Contents][Index]
Iteration commands bind local variables and determine when the loop ends. If no command sets an ending condition, then the loop runs forever. Infinite loops can be exited by using early-exit commands (Early Exit) or boolean commands (Checking Conditions).
Iteration commands must occur in the top level of the loopy
form or in a
sub-loop command (Sub-Loops). Using them elsewhere and trying to do something
like the below example will signal an error.
;; Signals an error: (loopy (list i '(1 2 3 4 5)) (when (cl-evenp i) ;; Can't use `list' inside `when'. ;; Will signal an error. (list j '(6 7 8 9 10)) (collect j)))
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.
Because some iteration commands use their variable to manage state, it is an error to use the same iteration variable for multiple iteration commands.
;; 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))
Iteration variables are initialized to nil
and they are updated at the point
in the loop body corresponding to the loop command’s position in the macro’s
arguments.
;; `elem' retains its value from the previous ;; iteration until it is updated again: ;; ;; => (((1 . nil) ; before ;; (2 . 1) ;; (3 . 2) ;; (4 . 3)) ;; ((1 . 1) ; after ;; (2 . 2) ;; (3 . 3) ;; (4 . 4))) (loopy (numbers nth :from 1) (collect elem-before (cons nth elem)) (list elem '(1 2 3 4)) (collect elem-after (cons nth elem)) (finally-return elem-before elem-after))
Be aware that cl-loop
does not consistently initialize its iteration variables
to nil. For some of cl-loop
’s iteration (‘for’) statements, the variable is
initialized to its value for the first iteration step and is manipulated
directly at the end of the iteration step. Loopy avoids this, as seen in the
below example, but that can result in unnecessary indirection for some use
cases, which has a minor speed cost.
;; => (5 (1 2 3 4) (1 2 3 4)) (cl-loop for elem in (list 1 2 3 4) collect num into nums-1 for num from 1 collect num into nums-2 finally return (list num nums-1 nums-2)) ;; => (4 (nil 2 3 4) (1 2 3 4)) (loopy (list elem (list 1 2 3 4)) (collect nums-1 num) (numbers num :from 1) (collect nums-2 num) (finally-return num nums-1 nums-2))
Generally, iteration commands with conditions check whether to terminate the
loop before the next iteration is run. They do not check their conditions
while running the current iteration. In the below example, note that the final
value of i
is 2 and not 3, even though the ‘do’ command (similar to
cl-loop
’s ‘do’ keyword) is placed before the ‘list’ command. Even though i
is updated before elem
is updated, the ‘list’ command’s condition that decides
whether to continue the loop is checked before the code in the ‘do’ command is
run.
;; => 2, not 3 (let ((i 0)) (loopy (do (setq i (1+ i))) (list elem '(0 1))) i)
If you do wish to conditionally leave the loop during an iteration, consider using the ‘leave’ and ‘leave-from’ commands (Early Exit).
;; => (3 (0 1)) (loopy (with (some-list (list 0 1)) (i 0)) (do (setq i (1+ i))) (when (null some-list) (leave)) (collect elems (car some-list)) (do (setq some-list (cdr some-list))) (finally-return i elems))
Unlike cl-loop
and like Common Lisp’s iterate
, arguments of the iteration
commands are evaluated only once. For example, while iterating through numbers,
you can’t suddenly change the direction of the iteration in the middle of the
loop, nor can you change the final numeric value. Similarly, the function used
to iterate through the list in the ‘list’ command is the same for the entire
loop. This restriction allows for producing more efficient code.
Next: Numeric Iteration, Up: Iteration [Contents][Index]
Run the loop for ‘EXPR’ iterations.
If given, then during the loop, ‘VAR’ is set to the number of iteration steps that have been run (0 for the first iteration step).
If ‘EXPR’ is 0, then the loop isn’t run.
‘(cycle VAR EXPR)’ works the same as ‘(numbers VAR :from 0 :below EXPR)’ (Numeric Iteration).
This command also has the aliases ‘cycling’ and ‘repeating’.
;; => (10 10 10) (loopy (with (i 10)) (cycle 3) (collect i)) ;; => (10 0 10 1 10 2) (loopy (with (i 10)) (repeat j 3) (collect i) (collect j)) ;; Same as above: ;; ;; => (10 0 10 1 10 2) (loopy (with (i 10)) (numbers j :from 0 :below 3) (collect i) (collect j)) ;; An argument of 0 stops the loop from running: ;; => nil (loopy (cycle 0) (return 'return-command-ran))
Iterate through the values returned by an Emacs Lisp iterator ((elisp)Generators). ‘EXPR’ is an iterator object produced by a calling a generator function. If given, ‘VAR’ holds the value yielded by the iterator. The loop ends when the iterator finishes.
‘close’ is whether the generator should be closed via iter-close
after the
loop ends. The default is t
. Note that Emacs will eventually close
un-closed, un-reachable generators during garbage collection. To be
consistent with other commands, ‘close’ is evaluated at the start of the loop,
even though it’s value is only used after the loop finishes.
‘yield-result’ is the optional second argument to the function iter-next
,
which is the value of iter-yield
in the iterator (not to be confused with
the value yielded by calling iter-next
). Unlike ‘close’, which is evaluated
once, ‘yield-result’ is an expression which is substituted into the loop body.
Therefore, ‘yield-result’ can be used to repeatedly call functions.
This command also has the name ‘iterating’.
;; With var: ;; ;; => ((1 . 4) (2 . 5) (3 . 6)) (loopy (with (iter-maker (iter-lambda (x) (while x (iter-yield (pop x)))))) (iter i (funcall iter-maker (list 1 2 3))) (iter j (funcall iter-maker (list 4 5 6))) (collect (cons i j))) ;; Without var: ;; ;; => (1 2 3) (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)) ;; Using `yield-result': ;; ;; => (3 2 1) (loopy (with (yield-results nil)) (set i 1 (1+ i)) (iter (funcall (iter-lambda () ;; The value from the expression specified by ;; `:yield-result' is `push'-ed: (push (iter-yield 'first-yield) yield-results) (push (iter-yield 'second-yield) yield-results) (push (iter-yield 'third-yield) yield-results))) ;; Note that the value of `i' evaluated each time: :yield-result i) (finally-return yield-results))
Warning: 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.
;; => 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))))
Next: Sequence Iteration, Previous: Generic Iteration, Up: Iteration [Contents][Index]
For iterating through numbers, there is the general ‘numbers’ command, and its variants ‘numbers-up’ and ‘numbers-down’.
Iterate through numbers. ‘KEYS’ is one or several of ‘from’, ‘upfrom’, ‘downfrom’, ‘to’, ‘upto’, ‘downto’, ‘above’, ‘below’, ‘by’, and ‘test’.
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 the command ‘(list i (number-sequence 1 10))’, and ‘(numbers i 3)’ is similar to ‘(set i 3 (1+ i))’.
In its most basic form, ‘numbers’ iterates from a starting value to an inclusive ending value using the ‘:from’ and ‘:to’ keywords, respectively.
;; => (1 2 3 4 5) (loopy (numbers i :from 1 :to 5) (collect i))
Unlike cl-loop
, ‘VAR’ is not initialized to the starting value given.
Instead, ‘VAR’ is updated during the loop, like in other iteration
commands. This avoids unexpectedly changing the value of ‘VAR’ after the
iteration step, as happens with some implementations of Common Lisp’s loop
macro (such cl-loop
).
;; => (4 (1 2 3 4)) (loopy (list elem (list 1 2 3 4)) (numbers num :from 1) (collect nums num) (finally-return num nums)) ;; => (5 (1 2 3 4)) (cl-loop for elem in (list 1 2 3 4) for num from 1 collect num into nums finally return (list num nums)) ;; SBCL returns 4, not 5: ;; ;; => (4 (1 2 3 4)) (loop for elem in (list 1 2 3 4) for num from 1 collect num into nums finally (return (list num nums)))
If the ending value is not given, then the value is incremented by 1 without end.
;; => (7 8 9 10 11 12 13 14 15 16) (loopy (cycle 10) (numbers i :from 7) (collect i))
To specify the step size, one can use the keyword ‘:by’. Except when ‘:test’ is given, the value for ‘:by’ must be positive. Other keyword arguments (‘:upfrom’, ‘:downfrom’, ‘:upto’, ‘:downto’, ‘:above’, and ‘:below’) control whether the variable is incremented or decremented.
;; => (1 3 5) (loopy (numbers i :from 1 :to 5 :by 2) (collect i)) ;; => (7 9 11 13 15 17 19 21 23 25) (loopy (cycle 10) (numbers i :from 7 :by 2) (collect i)) ;; => (1 2.5 4.0) (loopy (numbers i :from 1 :to 5 :by 1.5) (collect i))
By default, the variable’s value starts at 0 and increases by 1. To specify whether the value should be increasing or decreasing when using the ‘:by’ keyword, one can use the keywords ‘:downfrom’, ‘:downto’, ‘:upfrom’, ‘:upto’, ‘:above’, and ‘:below’. The keywords ‘:from’ and ‘:to’ don’t by themselves specify a direction, and they can be used without conflict with the keyword arguments that do. Using arguments that contradict one another will signal an error.
;; => (3 2 1) (loopy (cycle 3) (numbers i :downfrom 3) (collect i)) ;; => (0 -1 -2 -3) (loopy (numbers i :downto -3) (collect i)) ;; => (10 9 8 7 6 5 4 3 2) (loopy (numbers i :downfrom 10 :to 2) (collect i)) ;; => (10 8 6 4 2) (loopy (numbers i :from 10 :downto 2 :by 2) (collect i)) ;; => (1 2 3 4 5 6 7) (loopy (numbers i :from 1 :upto 7) (collect i)) ;; Signals an error: (loopy (numbers i :downfrom 10 :upto 20) (collect i))
To specify an exclusive ending value, use the keywords ‘:below’ for increasing values and ‘:above’ for decreasing values.
;; => (1 2 3 4 5 6 7 8 9) (loopy (numbers i :from 1 :below 10) (collect i)) ;; Same as above: (loopy (set i 1 (1+ i)) (while (< i 10)) (collect i)) ;; => (10 9 8 7 6 5 4 3 2) (loopy (numbers i :from 10 :above 1) (collect i)) ;; => (0 -1 -2) (loopy (numbers i :above -3) (collect i))
If you do not know whether you will be incrementing or decrementing, you can
use the keyword argument ‘test’, whose value is a function that should return
a non-nil value if the loop should continue, such as #'<=
. The function
receives ‘VAR’ as the first argument and the final value as the second
argument, as in (funcall TEST VAR FINAL-VAL)
. ‘test’ can only be used with
‘from’ and ‘to’; it cannot be used with keywords that already describe a
direction and ending condition. To match the behavior of cl-loop
, the
default testing function is #'<=
. When ‘test’ is given, ‘by’ can be
negative. As there is no default end value when ‘test’ is given, ‘to’ must
also be given.
;; => (10 9.5 9.0 8.5 8.0 7.5 7.0 6.5 6.0 5.5) (loopy (with (start 10) (end 5) (func #'>) (step -0.5)) (numbers i :to end :from start :by step :test func) (collect i)) ;; Expands to similar code as above. ;; Note that with `:above', step must be positive. ;; ;; => (10 9.5 9.0 8.5 8.0 7.5 7.0 6.5 6.0 5.5) (loopy (with (start 10) (end 5) (step 0.5)) (numbers i :from start :above end :by step) (collect i)) ;; Signals an error because `:upto' implies a testing function already: (loopy (numbers i :from 1 :upto 10 :test #'<) (collect i))
If you prefer using positional arguments to keyword arguments, you can use the commands ‘numbers-up’ and ‘numbers-down’ to specify directions. These commands are simple wrappers of the above ‘numbers’ command.
Equivalent to ‘(numbers VAR :from START [:downto END] &key by)’. This command exists only for convenience.
This command also has the aliases ‘numsdown’ and ‘numbering-down’.
;; => (10 8 6 4 2) (loopy (numbers-down i 10 1 :by 2) (collect i)) ;; => (10 8 6 4 2) (loopy (numbers-down i 10 1 2) (collect i))
Equivalent to ‘(numbers VAR :from START [END] &key by)’. This command exists only for convenience.
This command also has the aliases ‘numsup’ and ‘numbering-up’.
;; => (1 3 5 7 9) (loopy (numbers-up i 1 10 :by 2) (collect i)) ;; => (1 3 5 7 9) (loopy (numbers-up i 1 10 2) (collect i))
Next: Sequence Index Iteration, Previous: Numeric Iteration, Up: Iteration [Contents][Index]
These commands provide various ways to iterate through sequences ((elisp)Sequences Arrays Vectors).
Instead of iterating through just one sequence, the ‘array’, ‘list’, and ‘seq’ commands can be given multiple sequences of various sizes. In such cases, the elements of the sequences are distributed, like in the distributive property from mathematics. A new sequence of distributed elements is created before the loop runs, and that sequence is used for iteration instead of the source sequences. As seen in the below example, the resulting behavior is similar to that of nested loops.
;; => ((1 3 6) (1 4 6) (1 5 6) (2 3 6) (2 4 6) (2 5 6)) (loopy (list i '(1 2) '(3 4 5) '(6)) (collect i)) ;; Gives the same result as this (let ((result nil)) (dolist (i '(1 2)) (dolist (j '(3 4 5)) (dolist (k '(6)) (push (list i j k) result)))) (nreverse result)) ;; and this (cl-loop for i in '(1 2) append (cl-loop for j in '(3 4 5) append (cl-loop for k in '(6) collect (list i j k))))
The ‘array’ and ‘sequence’ commands can use the same keywords as the ‘numbers’ command (Numeric Iteration) for working with the index and choosing a range of the sequence’s elements through which to iterate. In addition to those keywords, they also have an ‘index’ keyword, which names the variable used to store the accessed index during the loop.
;; => ((1 . 9) (3 . 6) (5 . 5) (7 . 3) (9 . 1)) (loopy (array i [10 9 8 6 7 5 4 3 2 1] :from 1 :by 2 :index ind) (collect (cons ind i)))
Keep in mind that if used with sequence distribution, these keywords affect
iterating through the sequence of distributed elements. That is, they do not
affect how said sequence is produced. In the example below, see that cddr
is
applied to the sequence of distributed elements. It is not applied to the
source sequences.
;; This code creates the sequence of distributed elements ;; ((1 4) (1 5) (1 6) (2 4) (2 5) (2 6) (3 4) (3 5) (3 6)) ;; and then moves through this sequence using `cddr'. ;; ;; => ((1 4) (1 6) (2 5) (3 4) (3 6)) (loopy (list i '(1 2 3) '(4 5 6) :by #'cddr) (collect i)) ;; Not the same as: ;; => ((1 4) (1 6) (3 4) (3 6)) (loopy (list i '(1 3) '(4 6)) (collect i))
Loop through the elements of the array ‘EXPR’. In Emacs Lisp, strings are arrays whose elements are characters.
This command also has the aliases ‘arraying’ and ‘stringing’.
‘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.
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 this new, resulting array of lists.
(loopy (array i [1 2 3]) (do (message "%d" i))) ;; => (1 3) (loopy (array i [1 2 3 4] :by 2) (collect i)) ;; Collects the integer values representing each character. ;; => (97 98 99) (loopy (string c "abc") (collect c)) ;; This is the same as using [(1 3) (1 4) (2 3) (2 4)]. ;; => ((1 3) (1 4) (2 3) (2 4)) (loopy (array i [1 2] [3 4]) (collect i)) ;; => ((1 3) (2 3)) (loopy (array i [1 2] [3 4] :by 2) (collect i))
Loop through the cons cells of ‘EXPR’. Optionally, find the cons cells via the function ‘by’ instead of ‘cdr’.
This command also has the alias ‘consing’.
;; => ((1 2 3) (2 3) (3)) (loopy (cons i '(1 2 3)) (collect coll i) (finally-return coll)) ;; => ((1 2 3 4 5 6) (3 4 5 6) (5 6)) (loopy (cons i '(1 2 3 4 5 6) :by #'cddr) (collect coll i) (finally-return coll))
Loop through each element of the list ‘EXPR’. Optionally, update the list using ‘by’ instead of ‘cdr’.
This command also has the alias ‘listing’.
If multiple lists are given, distribute the elements of the lists into one new list. In such cases, ‘by’ applies to the new list, not the arguments of the command.
;; => (1 4 7 10). (loopy (list i (number-sequence 1 10 3)) (collect i)) ;; => (1 3 5) (loopy (list i '(1 2 3 4 5 6) :by #'cddr) (collect i)) ;; => ((1 4) (1 5) (1 6) (2 4) (2 5) (2 6) (3 4) (3 5) (3 6)) (loopy (list i '(1 2 3) '(4 5 6)) (collect i)) ;; => ((1 4) (1 6) (2 5) (3 4) (3 6)) (loopy (list i '(1 2 3) '(4 5 6) :by #'cddr) (collect i))
Iterate through the dotted key-value
pairs of map ‘EXPR’, using the function map-pairs
from the ‘map.el’ library.
This library generalizes working with association lists (“alists”), property
lists (“plists”), hash-tables, and vectors.
This command also has the aliases ‘mapping’ and ‘mapping-pairs’.
In each dotted pair assigned to ‘VAR’, the car
is the key and the cdr
is
the value.
By default, only the unique keys are used. To disable this deduplication,
pass nil
to the ‘unique’ keyword argument.
In general, as a map in not necessarily a sequence, you should not rely on the order in which the key-value pairs are found. There is no guarantee that they be in the same order each time.
These pairs are created before the loop begins via map-pairs
. In other
words, the map ‘EXPR’ is not processed progressively, but all at once.
Therefore, this command can have a noticeable start-up cost when working with
very large maps.
;; => ((a . 1) (b . 2)) (loopy (map pair '((a . 1) (b . 2))) (collect pair)) ;; => ((a b) (1 2)) (loopy (map (key . value) '((a . 1) (b . 2))) (collect keys key) (collect values value) (finally-return keys values)) ;; => ((:a :b) (1 2)) (loopy (map (key . value) '(:a 1 :b 2)) (collect keys key) (collect values value) (finally-return keys values)) ;; NOTE: For vectors, the keys are indices. ;; => ((0 1) (1 2)) (loopy (map (key . value) [1 2]) (collect keys key) (collect values value) (finally-return keys values)) ;; => ((a b) (1 2)) (let ((my-table (make-hash-table))) (puthash 'a 1 my-table) (puthash 'b 2 my-table) (loopy (map (key . value) my-table) (collect keys key) (collect values value) (finally-return keys values)))
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:
Again, this can be disabled by setting ‘unique’ to nil.
;; A comparison of setting the `unique' key to nil: ;; ;; => ((a 1) (a 2) (b 3)) (loopy (map (key . val) '((a . 1) (a . 2) (b . 3)) :unique nil) (collect (list key val))) ;; In this case, `list' has the same result: ;; => ((a 1) (a 2) (b 3)) (loopy (list (key . val) '((a . 1) (a . 2) (b . 3))) (collect (list key val))) ;; => ((:a 1) (:a 2) (:b 3)) (loopy (map (key . val) '(:a 1 :a 2 :b 3) :unique nil) (collect (list key val))) ;; In this case, `cons' has the same result: ;; => ((:a 1) (:a 2) (:b 3)) (loopy (cons (key val) '(:a 1 :a 2 :b 3) :by #'cddr) (collect (list key val)))
Loop through the sequence ‘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.
Note: For more on sequences, see (elisp)Sequences Arrays Vectors. This command works with the basic sequences understood by the Emacs Lisp functions
length
andelt
. It does not work with the generic sequences understood by the library ‘seq.el’. For those, use the ‘seq’ command.
This command also has the alias ‘sequencing’.
‘KEYS’ is one or several of ‘from’, ‘upfrom’, ‘downfrom’, ‘to’, ‘upto’, ‘downto’, ‘above’, ‘below’, ‘by’, ‘test’, and ‘index’. ‘index’ names the variable used to store the index being accessed. For the others, see the ‘numbers’ command.
Warning: Array elements can be accessed in constant time, but not list elements. For lists, the ‘sequence’ command is fastest when moving forwards through the list. In that case, the command does not have to search from the beginning of the list each time to find the next element. The ‘sequence’ command can be noticeably slower for lists when working backwards or when the ‘test’ parameter (for which direction cannot be assumed) is provided.
If multiple sequences are given, then these keyword arguments apply to the resulting sequence of distributed elements.
;; => (1 2 3) (loopy (sequence i [1 2 3]) (collect coll i) (finally-return coll)) ;; => (0 2 4) (loopy (sequence i [0 1 2 3 4 5] :by 2) (collect i)) ;; => (1 3 5) (loopy (sequence i [0 1 2 3 4 5 6] :by 2 :from 1 :to 5) (collect i)) ;; => (5 3 1) (loopy (sequence i '(0 1 2 3 4 5 6) :downfrom 5 :by 2 :to 1) (collect i)) ;; => ((1 3) (1 4) (2 3) (2 4)) (loopy (sequence i [1 2] '(3 4)) (collect i)) ;; => ((1 3) (2 3)) (loopy (sequence i [1 2] '(3 4) :by 2) (collect i))
For generic a sequence implementing the features of the library ‘seq.el’, loop through the generic sequence ‘EXPR’, binding ‘VAR’ to the elements of the sequence. Because it is more generic, ‘seq’ can be slower than the ‘sequence’ command, which in turn is somewhat less efficient than the ‘list’ and ‘array’ commands.
If multiple generic sequences are given, then these keyword arguments apply to the resulting generic sequence of distributed elements.
‘KEYS’ is one or several of ‘from’, ‘upfrom’, ‘downfrom’, ‘to’, ‘upto’, ‘downto’, ‘above’, ‘below’, ‘by’, ‘test’, and ‘index’. ‘index’ names the variable used to store the index being accessed. For the others, see the ‘numbers’ command.
This command also has the alias ‘seqing’.
The ‘seq’ command naively loops through the generic sequence using seq-elt
and seq-length
. Because other packages might implement custom sequences
using lists, no special consideration is made for optimizing the ‘seq’ command
when given a list.
Because the ‘seq’ command currently uses the function seq-length
to detect
when to leave the loop, it does not work with infinite sequences. For
infinite sequences, consider using the ‘stream’ command.
;; => (0 2 4) (loopy (seq i [0 1 2 3 4 5] :by 2) (collect i)) ;; => (5 3 1) (loopy (seq i '(0 1 2 3 4 5 6) :downfrom 5 :by 2 :to 1) (collect i)) ;; => ((1 3) (2 3)) (loopy (seq i [1 2] '(3 4) :by 2) (collect i))
Iterate through the elements for the stream ‘EXPR’. If ‘by’ is non-nil (default: 1), then move to the next n-th element during each iteration. This command is a special case of the ‘substream’ command (described below), setting ‘VAR’ to the first element of each substream. For more information on streams, see the command ‘substream’.
This command also has the alias ‘streaming’.
;; => (0 1 2) (loopy (stream i (stream [0 1 2])) (collect i)) ;; Same as the above: ;; => (0 1 2) (loopy (substream i (stream [0 1 2])) (collect (stream-first i)))
Iterate through the sub-streams of stream ‘EXPR’, similar to the command ‘cons’. If ‘by’ is non-nil (default: 1), then move to the next n-th substream during each iteration. If ‘length’ is given, then the substream bound to ‘VAR’ is only the specified length.
This command operates on the ‘stream’ type defined by the library ‘stream’
from GNU ELPA, which is not to be confused with the Emacs Lisp “input streams”
and “output streams” used for reading and printing text ((elisp)Read and Print). The “streams” defined by the ‘stream’ library are like lazy sequences
and are compatible with features from the built-in ‘seq’ library, such as
seq-elt
and seq-do
.
Sub-streams can only be destructured using the ‘&seq’ feature of the default destructuring method (Basic Destructuring), or by using the ‘seq’ flag (Using Flags). Streams are neither lists nor arrays.
This command also has the alias ‘substreaming’.
(require 'stream) ;; => (0 1 2) (loopy (substream i (stream [0 1 2])) (collect (stream-first i))) ;; => ((0 1 2) ;; (1 2 nil) ;; (2 nil nil)) (loopy (substream [&seq i j k] (stream [0 1 2])) (collect (list i j k))) ;; => ((0 1) ;; (1 2) ;; (2 3) ;; (3 nil)) (loopy (flag seq) ;; Using the `seq.el' library to destructure, ;; not destructuring as a list: (substream (i j) (stream '(0 1 2 3))) (collect (list i j))) ;; => ((0 1 2 3 4 5) ;; (2 3 4 5) ;; (4 5)) (loopy (substream i (stream [0 1 2 3 4 5]) :by 2) (set inner-result nil) (do (seq-do (lambda (x) (push x inner-result)) i)) (collect (reverse inner-result))) ;; => ((0 1) ;; (2 3) ;; (4 5)) (loopy (set inner-result nil) ;; Using `:length' limits the length of the substream ;; bound to `i'. (substream i (stream [0 1 2 3 4 5]) :by 2 :length 2) (do (seq-do (lambda (x) (push x inner-result)) i)) (collect (reverse inner-result)))
Next: Sequence Reference Iteration, Previous: Sequence Iteration, Up: Iteration [Contents][Index]
This command is for iterating through a sequence’s indices without accessing the actual values of that sequence. This is helpful if you know ahead of time that you are only interested in a small subset of the sequence’s elements.
As with the ‘array’ and ‘seq’ commands, the ‘seq-index’ command can use the same keywords as the ‘numbers’ command (Numeric Iteration) for working with the index and choosing a range of the sequence elements through which to iterate.
Iterate through the indices of ‘EXPR’.
There is only one implementation of this command; there are no type-specific versions. This command also has the following aliases:
‘KEYS’ is one or several of ‘from’, ‘upfrom’, ‘downfrom’, ‘to’, ‘upto’, ‘downto’, ‘above’, ‘below’, ‘by’, and ‘test’. For their meaning, see the ‘numbers’ command. This command is very similar to ‘numbers’, except that it can automatically end the loop when the index of the final element is reached. With ‘numbers’, one would first need to explicitly calculate the length of the sequence.
;; => (97 98 99 100 101 102) (loopy (with (my-string "abcdef")) (string-index idx my-string) (collect (aref my-string idx))) ;; Works the same as (loopy (with (my-string "abcdef")) (numbers idx :from 0 :below (length my-string)) (collect (aref my-string idx)))
This command does not support destructuring.
;; => (0 1 2) (loopy (sequence-index i [1 2 3]) (collect i)) ;; => (0 1 2) (loopy (array-index i "abc") (collect i)) ;; => (0 1 2) (loopy (list-index i '(1 2 3)) (collect i)) ;; => (8 6 4 2) (loopy (with (my-seq [0 1 2 3 4 5 6 7 8 9 10])) (sequence-index idx my-seq :from 8 :downto 1 :by 2) (collect (elt my-seq idx)))
Previous: Sequence Index Iteration, Up: Iteration [Contents][Index]
These commands all iterate through setf
-able places as generalized
variables ((elisp)Generalized Variables). These generalized variables
are commonly called “references”, “fields”, or “places”. The below example
demonstrates using (nth 1 my-list)
and (aref my-array 1)
as setf
-able
places.
;; => (1 99 3 4 5) (let ((my-list '(1 2 3 4 5))) (setf (nth 1 my-list) 99) my-list) ;; => [(1 2 3) (4 . 99)] (let ((my-array [(1 2 3) (4 5 6)])) (setf (cdr (aref my-array 1)) 99) my-array)
Like other commands, “field” or “reference” commands can also use
destructuring, in which case the fields/places of the sequence are
destructured into “sub-fields”, like the cdr
of the second array element
in the example above.
Caution: Be aware that using
setf
on an array sub-sequence named by ‘&rest’ will only overwrite values, not truncate or grow the array.
Warning: Unfortunately, not all kinds of recursive destructuring work on references.
Currently:
- ‘&optional’ variables are not supported
- ‘SUPPLIED’ variables are not supported for ‘&key’ and ‘&map’.
- Non-nil default values for ‘&optional’, ‘&key’, and ‘&map’ are not supported.
As with the ‘array’ and ‘seq’ commands, the ‘array-ref’ and ‘seq-ref’ commands can use the same keywords as the ‘numbers’ command (Numeric Iteration) for working with the index and choosing a range of the sequence elements through which to iterate. In addition to those keywords, they also have an ‘index’ keyword, which names the variable used to store the accessed index during the loop.
Loop through the elements of
the array ‘EXPR’, binding ‘VAR’ as a setf
-able place.
This command also has the aliases ‘arraying-ref’ and ‘stringing-ref’.
‘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.
;; => "aaa" (loopy (with (my-str "cat")) (array-ref i my-str) (do (setf i ?a)) (finally-return my-str)) ;; => "0a2a4a6a89" (loopy (with (my-str "0123456789")) (array-ref i my-str :from 1 :by 2 :to 7) (do (setf i ?a)) (finally-return my-str)) ;; Works the same as (loopy (with (my-str "0123456789")) (numbers idx 1 7 :by 2) (do (setf (aref my-str idx) ?a)) (finally-return my-str))
Loop through the elements of the list ‘EXPR’,
binding ‘VAR’ as a setf
-able place. Optionally, update the list via
function ‘by’ instead of cdr
.
This command also has the aliases ‘listing-ref’.
;; => (7 7 7) (loopy (with (my-list '(1 2 3))) (list-ref i my-list) (do (setf i 7)) (finally-return my-list)) ;; Works similar to (loopy (with (my-list '(1 2 3))) (numbers idx :below (length my-list)) (do (setf (nth idx my-list) 7)) (finally-return my-list)) ;; => (7 2 7) (loopy (with (my-list '(1 2 3))) (list-ref i my-list :by #'cddr) (do (setf i 7)) (finally-return my-list)) ;; => ([1 7] [2 7]) (loopy (with (my-list '([1 2] [2 3]))) (list-ref [_ i] my-list) (do (setf i 7)) (finally-return my-list))
Loop through the values of map ‘EXPR’,
binding ‘VAR’ as a setf
-able place. Like the command ‘map’, this command
uses the ‘map.el’ library.
This command also has the alias ‘mapping-ref’.
‘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. 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 via the function
map-keys
before the loop is run, which can be expensive for large maps.
Similar to ‘map’, any duplicate keys are ignored by default. This can be disabled by setting the ‘unique’ keyword argument to nil, though note that using such duplicate keys will still refer to the value of the first occurence. There is no way to use a duplicate key to refer to the duplicate’s value.
;; Duplicate keys are ignored by default. ;; ;; => (:a 8 :a 'ignored :b 10) (loopy (with (map (list :a 1 :a 'ignored :b 3))) (map-ref i map) (do (cl-incf i 7)) (finally-return map)) ;; If duplicates are not ignored: ;; ;; => (:a 15 :a ignored :b 10) (loopy (with (map (list :a 1 :a 'ignored :b 3))) (map-ref i map :unique nil) (do (cl-incf i 7)) (finally-return map)) ;; Getting the key using `key': ;; ;; => (((cat . 7) ; The map itself ;; (dog . 7) ;; (zebra . 7)) ;; (cat dog zebra)) ; The keys (loopy (with (map (list (cons 'cat 1) (cons 'dog 2) (cons 'zebra 3)))) (map-ref i map :key my-key) (do (setf i 7)) (collect my-key) (finally-return map loopy-result))
Loop through the elements of the
sequence ‘EXPR’ (an array or list), binding ‘VAR’ as a setf
-able place.
This command also has the aliases ‘sequencing-ref’.
‘KEYS’ is one or several of ‘from’, ‘upfrom’, ‘downfrom’, ‘to’, ‘upto’, ‘downto’, ‘above’, ‘below’, ‘by’, ‘test’, and ‘index’. ‘index’ names the variable used to store the index being accessed. For others, see the ‘numbers’ command.
;; => (7 7 7 7) (loopy (with (my-seq (list 1 2 3 4))) (sequence-ref i my-seq) (do (setf i 7)) (finally-return my-seq)) ;; => (0 cat 2 cat 4 cat 6 cat 8 cat) (loopy (with (my-list (list 0 1 2 3 4 5 6 7 8 9))) (sequence-ref i my-list :from 1 :by 2 ) (do (setf i 'cat)) (finally-return my-list)) ;; => "0123456a8a" (loopy (with (my-str (copy-sequence "0123456789"))) (sequence-ref i my-str :downto 6 :by 2 ) (do (setf i ?a)) (finally-return my-str))
Loop through the elements of the generic
sequence ‘EXPR’, via the features of the library ‘seq.el’, binding ‘VAR’ as a
setf
-able place.
Note: Not all generic sequences are mutable, so not all generic sequences work as a
setf
-able place.
‘KEYS’ is one or several of ‘from’, ‘upfrom’, ‘downfrom’, ‘to’, ‘upto’, ‘downto’, ‘above’, ‘below’, ‘by’, ‘test’, and ‘index’. ‘index’ names the variable used to store the index being accessed. For others, see the ‘numbers’ command.
This command also has the alias ‘seqing-ref’.
The ‘seq-ref’ command naively loops through the generic sequence using
seq-elt
and seq-length
. Because other packages might implement custom
sequences using lists, no special consideration is made for optimizing the
‘seq-ref’ command when given a list.
Because the ‘seq-ref’ command currently uses the function seq-length
to
detect when to leave the loop, it does not work with infinite sequences.
;; => (7 7 7 7) (loopy (with (my-seq (list 1 2 3 4))) (seq-ref i my-seq) (do (setf i 7)) (finally-return my-seq)) ;; => (0 cat 2 cat 4 cat 6 cat 8 cat) (loopy (with (my-list (list 0 1 2 3 4 5 6 7 8 9))) (seq-ref i my-list :from 1 :by 2 ) (do (setf i 'cat)) (finally-return my-list)) ;; => "0123456a8a" (loopy (with (my-str (copy-sequence "0123456789"))) (seq-ref i my-str :downto 6 :by 2 ) (do (setf i ?a)) (finally-return my-str))
Next: Checking Conditions, Previous: Iteration, Up: Loop Commands [Contents][Index]
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.
Unlike iteration commands, you can refer to the same accumulation variable in multiple accumulation commands if needed.
;; => (1 6 2 7 3 8) (loopy (list i '(1 2 3)) (collect coll i) (collect coll (+ i 5)) (finally-return coll))
Note: Keep in mind that it is an error to modify accumulation variables outside of accumulation commands. This restriction allows accumulations to be much faster.
Similar to iteration commands, accumulation commands can also use destructuring. In accumulation commands, the values resulting from destructuring are accumulated, instead of the destructured value.
;; => ((1 4) (2 5) (3 6)) (loopy (list elem '((1 2 3) (4 5 6))) (collect (coll1 coll2 coll3) elem) (finally-return coll1 coll2 coll3)) ;; => (5 7 9) (loopy (list elem '((1 2 3) (4 5 6))) (sum (sum1 sum2 sum3) elem) (finally-return sum1 sum2 sum3)) ;; Returns the same values as above. (loopy (list elem '((1 2 3) (4 5 6))) (set sum1 (cl-first elem) (+ sum1 (cl-first elem))) (set sum2 (cl-second elem) (+ sum2 (cl-second elem))) (set sum3 (cl-third elem) (+ sum3 (cl-third elem))) (finally-return sum1 sum2 sum3))
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
understood to be the return value of the loop. These implied return values can
be overridden by using the the ‘return’ and ‘return-from’ loop commands or the
‘finally-return’ macro argument.
;; => (1 2 3) (cl-loop for i from 1 to 3 collect i) ;; => (1 2 3) (loopy (numbers i :from 1 :to 3) (collect i))
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.
;; => (0 1 2 3 4 5) (loopy (numbers i :from 1 :to 10) (when (> i 5) (leave)) (collect i) (finally-return (cons 0 loopy-result)))
In general, you should not attempt to modify or use the value of loopy-result
during the loop, as it is not guaranteed to have a correct value when
efficiently building sequences. For example, it is often faster to build a list
in reverse instead of appending to its end. For some commands, such as those in
Numeric Accumulation and Generic Accumulation, this does not matter.
Be aware that explicitly named accumulation variables do not affect the implied return value of a loop. Such values must be returned explicitly, or they will be ignored when the macro returns a value. This limitation is needed for more consistently handling the complexity that comes from allowing unknown kinds of destructuring via the alternative destructuring systems. This may change in the future.
;; 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) (collect my-explicit-variable (* 2 i)))
Therefore, when mixing implicit and explicit accumulation variables, you must always use the ‘finally-return’ special macro argument to return all of the accumulation results.
;; => ((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) ; 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))
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 signal an error.
For example, you should not try to accumulate ‘collect’ results and ‘sum’ 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.
;; Incompatible commands: ;; => ERROR (loopy (numbers i :from 1 :to 3) (collect i) (sum i)) ;; Compatible commands: ;; => 27 (loopy (with (loopy-result 0)) (numbers i :from 1 :to 3) (sum i) (multiply i))
Each accumulation command has a default initialization value for the
accumulation variable. For most commands, this is nil
. This documentation
tries to note when it is not nil
. For example, the default starting value for
the ‘sum’ command is 0
and the default starting value for the ‘multiply’
command is 1
. The default initialization value used by an accumulation
command can be overridden using the ‘with’ special macro argument.
Warning: Currently, a warning is raised when the default initial values of accumulation commands conflict. In the future, this will be an error. To resolve this conflict, use the ‘with’ special macro argument, as noted above.
;; Raises a warning. Will raise an error in the future. ;; ;; => 27 (loopy (numbers i :from 1 :to 3) (sum my-accum i) ; Defaults to 0. (multiply my-accum i) ; Defaults to 1. (finally-return my-accum)) ;; No warning because using `with': ;; ;; => 87 (loopy (with (my-accum 10)) (numbers i :from 1 :to 3) (sum my-accum i) ; Default not used. (multiply my-accum i) ; Default not used. (finally-return my-accum))
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
mentioned loopy-result
) have the correct value during the loop. For example,
loopy
will construct named accumulation variables containing lists in the
correct order, instead of using the more efficient push
-nreverse
idiom.
This behavior can be disabled by optimizing accumulations using the ‘accum-opt’
special macro argument (Optimizing Accumulations).
Below are examples of an optimized accumulation and an un-optimized accumulation. See that the example expansion of the un-optimized accumulation is more complex and uses a slower way of building the accumulated list.
;; Optimized accumulation: ;; ;; => (1 3 2 6 3 9) (loopy (accum-opt coll) (numbers i :from 1 :to 3) (collect coll i) (collect coll (* i 3)) (finally-return coll)) ;; Optimized example expansion: ;; ;; => (1 3 2 6 3 9) (let* ((coll nil) (i 1) (nums-end192 3) (nums-increment191 1)) (cl-block nil (while (<= i nums-end192) (setq coll (cons i coll)) (setq coll (cons (* i 3) coll)) (setq i (1+ i))) (setq coll (nreverse coll))) coll)
;; Unoptimized accumulation: ;; ;; => (1 3 2 6 3 9) (loopy (numbers i :from 1 :to 3) (collect coll i) (collect coll (* i 3)) (finally-return coll)) ;; Unoptimized example expansion: ;; ;; => (1 3 2 6 3 9) (let* ((coll nil) (coll-last-link-190 coll) (i 1) (nums-end189 3) (nums-increment188 1)) (cl-block nil (while (<= i nums-end189) (cond (coll-last-link-190 (setcdr coll-last-link-190 (list i)) (setq coll-last-link-190 (cdr coll-last-link-190))) (coll (setq coll-last-link-190 (last coll)) (setcdr coll-last-link-190 (list i)) (setq coll-last-link-190 (cdr coll-last-link-190))) (t (setq coll (list i) coll-last-link-190 coll))) (cond (coll-last-link-190 (setcdr coll-last-link-190 (list (* i 3))) (setq coll-last-link-190 (cdr coll-last-link-190))) (coll (setq coll-last-link-190 (last coll)) (setcdr coll-last-link-190 (list (* i 3))) (setq coll-last-link-190 (cdr coll-last-link-190))) (t (setq coll (list (* i 3)) coll-last-link-190 coll))) (setq i (1+ i)))) coll)
Warning: You should not try to access implied (or optimized) accumulation results (for example,
loopy-result
) while the loop is running. Implied results are only required to be correct after the loop ends (before code in ‘else-do’ is run), allowing for more efficient code.Furthermore, because using a ‘return’ or ‘return-from’ command overrides implied return values, using these commands can prevent implied accumulation results from being finalized. Using the ‘leave’ command, which exits the loop without returning a value, does not affect the correctness of implied results.
Next: Generic Accumulation, Up: Accumulation [Contents][Index]
You will notice that each accumulation command has an alias of the command name
in the present participle form (the “-ing” form). For example, instead of
“minimize”, you can use “minimizing”. Instead of “sum” and “append”, you can
use “summing” and “appending”. This is similar to the behavior of cl-loop
,
and helps to avoid name collisions when using the loopy-iter
macro
(The loopy-iter
Macro).
Some accumulation commands have optional keyword parameters, which are listed in the command’s definition. To avoid repetition, the common parameters are all described below.
Where to place a value. One of ‘end’, ‘start’, or ‘beginning’ (equivalent to ‘start’). If ungiven, defaults to ‘end’. These positions need not be quoted.
;; => (1 2 3) (loopy (list i '(1 2 3)) (collect i :at end)) ;; => (3 2 1) (loopy (list i '(1 2 3)) (collect i :at start))
An alternative way to specify the variable into which to
accumulate values. One would normally just give ‘VAR’ as the first
argument of the loop command, but if you wish, you can use this keyword
argument for a more cl-loop
-like syntax.
As all accumulation commands support this keyword, it is not listed in any command definition.
;; => (1 2 3) (loopy (list i '(1 2 3)) (collect my-collection i) (finally-return my-collection)) ;; => (1 2 3) (loopy (list i '(1 2 3)) (collect i :into my-collection) (finally-return my-collection))
A function of two arguments, usually used to test for equality. This function is normally used to test if a value is already present in the accumulating sequence. If so, the function should return a non-nil value.
Note: This argument is similar to the ‘:test’ argument used by ‘cl-lib’, but is closer to the optional ‘testfn’ argument used by ‘seq’ (for example, in
seq-contains-p
). There are two important differences:
- Tests default to
equal
, like in other Emacs Lisp libraries, noteql
.- The first argument is the existing value or sequence and the second argument is the tested value. This is the opposite of the order used by
cl-member
andmemq
.
;; Only add items to the list whose `car's are not already present ;; or whose `cdr' is not 3: ;; ;; => ((a . 1) (c . 4)) (loopy (with (test-fn (lambda (seq-val new-val) (or (equal (cdr new-val) 3) (eq (car seq-val) (car new-val)))))) (list i '((a . 1) (a . 2) (b . 3) (c . 4))) (adjoin i :test test-fn))
A one-argument function that transforms both the tested value and the value from sequence used by the ‘test’ keyword.
The keyword ‘key’ is useful to avoid applying a transforming function to the tested value more than once when searching through a long sequence, as would be done if it were called explicitly in ‘test’.
;; => ((a . 1) (b . 2) (c . 4)) (loopy (with (test #'car)) (list i '((a . 1) (b . 2) (a . 3) (c . 4))) (adjoin i :at end :key #'car)) ;; Similary to the above: ;; ;; => ((a . 1) (b . 2) (c . 4)) (loopy (with (test-val)) (list i '((a . 1) (b . 2) (a . 3) (c . 4))) (set test-val (car i)) (adjoin i :test (lambda (seq-val _) (equal (car seq-val) test-val))))
The arguments to the ‘test’ and ‘key’ parameters can be quoted functions or
variables, just like when using cl-union
, cl-adjoin
, and so on. loopy
knows how to expand efficiently for either case.
Next: Numeric Accumulation, Previous: Common Properties of Accumulation Commands, Up: Accumulation [Contents][Index]
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.
cl-reduce
, calling a function that receives (1) the
accumulation variable and (2) the value to accumulate, in that order.
The commands are described in more detail below.
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’).
This command also has the alias ‘reducing’.
Note that the first accumulated value depends on the initial value of ‘VAR’.
By default, the first accumulated value is the value of ‘EXPR’, not a result
of calling ‘FUNC’. However, if ‘VAR’ has an initial value given by the ‘with’
special macro argument, then the first accumulated value is the result of
(funcall FUNC VAR EXPR)
, as also done in the subsequent steps of the loop.
This use of ‘with’ is similar to the ‘:initial-value’ keyword argument used by
cl-reduce
.
;; => 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)
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.
(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))
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.
;; Call `(cons i my-accum)' ;; ;; => (2 1) (loopy (list i '(1 2)) (accumulate my-accum i #'cons) (finally-return my-accum)) ;; Works mostly the same as the above: (loopy (list i '(1 2)) (set my-accum (cons i my-accum)) (finally-return my-accum)) ;; => ((3 1) (4 2 8 9 10)) (loopy (with (accum1 nil) (accum2 (list 8 9 10))) (list i '((1 2) (3 4))) (accumulate (accum1 accum2) i #'cons) (finally-return accum1 accum2))
This command also has the alias ‘callf2’. It is similar to using the function
cl-callf2
, except that the function argument is given last and must be
quoted. This alias is intended to help users remember argument order.
(loopy (list i '(1 2)) (callf2 my-accum i #'cons) (finally-return my-accum)) ;; Is the same as the above: (loopy (with (my-accum)) (list i '(1 2)) (do (cl-callf2 cons i my-accum)) (finally-return my-accum))
Set the accumulation variable ‘VAR’ to the value of ‘EXPR’.
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.
;; => 6 (loopy (with (loopy-result 0)) (array i [1 2 3]) (set-accum (+ loopy-result i))) ;; These are equivalent to the above example: ;; => 6 (loopy (with (loopy-result 0)) (array i [1 2 3]) (set loopy-result (+ loopy-result i)) (finally-return loopy-result)) ;; => 6 (loopy (with (loopy-result 0)) (array i [1 2 3]) (set-accum loopy-result (+ loopy-result i)) (finally-return loopy-result))
Next: Sequence Accumulation, Previous: Generic Accumulation, Up: Accumulation [Contents][Index]
Numeric accumulation work on numbers, such as by repeatedly adding or multiplying values together.
Count the number of times that ‘EXPR’ evaluates to a non-nil value. ‘VAR’ starts at 0 and is incremented by 1 each time.
This command also has the alias ‘counting’.
;; => 3 (loopy (list i '(1 nil 3 nil 5)) (count non-nil-count i) (finally-return non-nil-count))
Repeatedly set ‘VAR’ to the greater of the values
‘VAR’ and ‘EXPR’. ‘VAR’ starts at negative infinity (-1.0e+INF
), so that
any other value should be greater that it.
This command also has the aliases ‘maximizing’ and ‘maxing’.
;; => 11 (loopy (list i '(1 11 2 10 3 9 4 8 5 7 6)) (maximize my-max i) (finally-return my-max))
Repeatedly set ‘VAR’ to the lesser of the values
‘VAR’ and ‘EXPR’. ‘VAR’ starts at positive infinity (1.0e+INF
), so that any
other value should be less than it.
This command also has the aliases ‘minimizing’ and ‘minning’.
;; => 0 (loopy (list i '(1 11 2 10 3 0 9 4 8 5 7 6)) (minimize my-min i) (finally-return my-min))
Repeatedly set ‘VAR’ to the product of the values ‘EXPR’ and ‘VAR’. ‘VAR’ starts at 1.
This command also has the alias ‘multiplying’.
;; => 120 (loopy (list i '(1 2 3 4 5)) (multiply 5-factorial i) (finally-return 5-factorial))
Repeatedly set ‘VAR’ to the sum of the values of ‘EXPR’ and ‘VAR’. ‘VAR’ starts at 0.
This command also has the alias ‘summing’.
;; => 10 (loopy (list i '(1 2 3 4)) (sum my-sum i) (finally-return my-sum))
Next: Other Accumulation Commands, Previous: Numeric Accumulation, Up: Accumulation [Contents][Index]
Sequence accumulation commands are used to join lists (such as ‘union’ and ‘append’) and to collect items into lists (such as ‘collect’ and ‘adjoin’).
Repeatedly add ‘EXPR’ to ‘VAR’ if it is not already present in the list.
This command also has the alias ‘adjoining’.
Unlike cl-adjoin
and like the other accumulation commands, this command
defaults to adjoining ‘EXPR’ to the end of ‘VAR’, not the beginning.
;; => ((1 . 1) (1 . 2) (2 . 3)) (loopy (list i '((1 . 1) (1 . 2) (1 . 2) (2 . 3))) (adjoin i)) ;; Coerced to a vector /after/ the loop ends. ;; => [1 2 3 4] (loopy (list i '(1 2 3 3 4)) (adjoin my-var i) (when (vectorp my-var) (return 'is-vector)) (finally-return my-var)) ;; => [4 3 2 1] (loopy (list i '(1 2 3 3 4)) (adjoin my-var i :at 'start) (finally-return my-var))
Repeatedly concatenate ‘EXPR’ to ‘VAR’, as if
by the function append
.
This command also has the alias ‘appending’.
;; => '(1 2 3 4 5 6) (loopy (list i '((1 2 3) (4 5 6))) (append coll i) (finally-return coll)) ;; => (4 5 6 1 2 3) (loopy (list i '((1 2 3) (4 5 6))) (append i :at start))
Collect the value of ‘EXPR’ into the list ‘VAR’. By default, elements are added to the end of the list.
This command also has the alias ‘collecting’.
;; => '(1 2 3) (loopy (list i '(1 2 3)) (collect i)) ;; => '((1 2 3) ((1) (1 2) (1 2 3))) (loopy (list i '(1 2 3)) ;; Collect `i' into `coll1'. (collect coll1 i) ;; Collect `coll1' into a generated variable. (collect coll1) (finally-return coll1 loopy-result)) ;; => [1 2 3] (loopy (list j '(1 2 3)) (collect j) (finally-return (cl-coerce loopy-result 'vector))) ;; => (3 2 1) (loopy (list j '(1 2 3)) (collect j :at start)) ;; => (1 2 3) (loopy (list j '(1 2 3)) (collect j :at 'end))
Repeatedly concat
the value of ‘EXPR’ onto
‘VAR’, as a string. For concatenating values into a vector, see the command
‘vconcat’.
This command also has the alias ‘concating’.
;; => "abc" (loopy (list i '("a" "b" "c")) (concat str i) (finally-return str)) ;; => ("da" "eb" "fc") (loopy (list j '(("a" "b" "c") ("d" "e" "f"))) (concat (str1 str2 str3) j :at 'start) (finally-return str1 str2 str3))
Repeatedly and destructively concatenate the
value of ‘EXPR’ onto ‘VAR’ as if by using the function nconc
.
This command also has the alias ‘nconcing’.
Caution:
nconc
is a destructive operation that modifies ‘VAR’ directly ((elisp)Rearrangement). This is important to keep in mind when working with literal values, such as the list ‘'(1 2 3)’, whose modification could apply wherever that value is used ((elisp)Self-Evaluating Forms).
;; => '(1 2 3 4 5 6 7 8) (loopy (list i '((1 2 3 4) (5 6 7 8))) (nconc my-new-list i) (finally-return my-new-list)) ;; => '(3 3 3 2 2 1) (loopy (list i (list (make-list 1 1) (make-list 2 2) (make-list 3 3))) (nconc i :at start))
Repeatedly and destructively insert into ‘VAR’ the elements of ‘EXPR’ which are not already present in ‘VAR’.
This command also has the alias ‘nunioning’.
;; => (4 1 2 3) (loopy (list i '((1 2) (2 3) (3 4))) (nunion var i) (finally-return var)) ;; => (4 2 (1 1) 3) (loopy (list i '(((1 1) 2) ((1 1) 3) (3 4))) (nunioning var i :test #'equal) (finally-return var)) ;; => ((1 2 3) (2 3 4)) (loopy (array i [((1 2) (2 3)) ((1 2 3) (3 4))]) (nunion (var1 var2) i :test #'equal) (finally-return var1 var2))
Repeatedly concatenate ‘EXPR’ onto the front of ‘VAR’,
as if by the function append
.
This command also has the alias ‘prepending’.
This command is interpreted by Loopy as ‘(append VAR EXPR :at start)’, and is normally described as such when reporting errors. It exists for clarity and convenience.
;; => (5 6 3 4 1 2) (loopy (array i [(1 2) (3 4) (5 6)]) (prepend i)) ;; => (4 3 2 1) (let ((my-list '(1))) (loopy (without my-list) (array elem [(2) (3) (4)]) (prepend my-list elem) (finally-return my-list)))
Collect the value of ‘EXPR’ into a list, adding
values to the front of ‘VAR’ as if by using the function push
.
This command also has the alias ‘pushing’ and ‘pushing-into’.
This command is interpreted by Loopy as ‘(collect VAR EXPR :at start)’, and is normally described as such when reporting errors. It exists for clarity and convenience.
;; => (3 2 1) (loopy (array i [1 2 3]) (push my-list i) (finally-return my-list))
Repeatedly insert into ‘VAR’ the elements of the list ‘EXPR’ that are not already present in ‘VAR’.
This command also has the alias ‘unioning’.
;; => (4 1 2 3) (loopy (list i '((1 2) (2 3) (3 4))) (union var i) (finally-return var)) ;; => (4 2 (1 1) 3) (loopy (list i '(((1 1) 2) ((1 1) 3) (3 4))) (unioning var i :test #'equal) (finally-return var)) ;; => ((1 2 3) (2 3 4)) (loopy (array i [((1 2) (2 3)) ((1 2 3) (3 4))]) (union (var1 var2) i :test #'=) (finally-return var1 var2))
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’.
;; => [1 2 3 4 5 6] (loopy (list i '([1 2 3] [4 5 6])) (vconcat my-vector i) (finally-return my-vector)) ;; => [4 5 6 1 2 3] (loopy (list i '([1 2 3] [4 5 6])) (vconcat i :at 'start))
Next: Optimizing Accumulations, Previous: Sequence Accumulation, Up: Accumulation [Contents][Index]
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’.
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.
To be consistent with other commands, ‘ON-FAILURE’ is evaluated at the start of the loop, even though that is not necessarily where it is used.
;; => (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 4 5 6)) (find i (> i 12))) ;; => 27 (loopy (list i '(1 2 3 4 5 6)) (find i (> i 12) :on-failure 27)) ;; => 27 (loopy (list i '(1 2 3 4 5 6)) (while (< i 3)) (find i (> i 12) :on-failure 27)) ;; => nil (loopy (list i '(1 2 3 4 5 6)) (find nil (> i 3) :on-failure 27)) ;; Value of `:on-failure' gotten at the start of the loop: ;; => 27 (loopy (with (on-fail 27)) (list i '(1 2 3)) (set on-fail 33) (find i (> i 4) :on-failure on-fail))
Previous: Other Accumulation Commands, Up: Accumulation [Contents][Index]
By default, named accumulation variables (excluding the automatically named
loopy-result
) are accessible during the loop, and their values are always
correct during execution. For example, lists are always in the correct order,
even when constructing linked lists in the reverse order would be more
efficient.
;; Note how `coll' is always in the correct order: ;; => ((1 2 3) ;; ((1) (1 2) (1 2 3))) (loopy (array i [1 2 3]) (collect coll i) (collect intermediate-values (copy-sequence coll)) (finally-return coll intermediate-values))
Implied accumulation variables are not required to always be in the correct order, so commands using such variables can produce more efficient code.
;; Similar in efficiency to the below: ;; => (2 3 4 5 6 7 8 9 10 11 12 13 ...) (loopy (list i (number-sequence 1 1000)) (collect (1+ i))) ;; => (2 3 4 5 6 7 8 9 10 11 12 13 ...) (let (result) (dolist (i (number-sequence 1 1000)) (push (1+ i) result)) (nreverse result))
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 accumulating
list, since that would be much slower for large lists.
;; `loopy' can be faster than the below `dolist' approach: ;; => (3 2 1 7 14 21) (loopy (list i '(1 2 3)) (collect i :at start) (collect (* i 7) :at end)) ;; For large accumulations, this is slower than the above: ;; => (3 2 1 7 14 21) (let (result) (dolist (i '(1 2 3)) (setq result (cons i result) result (nconc result (list (* i 7))))) result)
In such cases, loopy
will naively optimize placing values at whichever side of
the sequences appears to be more used. In the example below, note that even
though the commands to insert values at the front of the list are never actually
run, loopy
will still optimize for frontal insertions. Here, loopy
simply
counts that 2 commands seem to place values at the front of the list while only
1 command seems to place values at the end.
;; This code optimizes for insertions at the front of the list: ;; => (1 2 3) (loopy (list i '(1 2 3)) (collect i :at end) (when nil (collect i :at start) (collect i :at start)))
The special macro argument ‘accum-opt’ can be used to better control these
optimizations (Special Macro Arguments). With it, you can (1) treat an explicit
variable as if it were implicit and optionally (2) specify which side of a
sequence you expect to use more. The arguments passed to ‘accum-opt’ are either
symbols (such as loopy-result
) or lists of a symbol and a position. To be
clear, use of the variable loopy-result
is always at least naively optimized
in the manner described above.
In the example below, see that
coll
has been explicitly optimized for
using the end of the sequence, despite there being more commands that use the
beginning of the sequence.
coll
has been optimized, the order of values in
coll
need not be correct during the loop.
;; 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)) (list i '(1 2 3)) (collect coll i :at end) (collect coll (+ 10 i) :at start) (collect coll (+ 20 i) :at start) (collect intermediate-values (copy-sequence coll)) (finally-return coll intermediate-values))
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 need them to be so, you are recommended to use ‘accum-opt’ on those variables.
;; => ((1 3) ;; (2 4) ;; ((1) (3 1)) ;; ((2) (4 2))) (loopy (accum-opt a b) (array elem [(1 2) (3 4)]) (collect (a b) elem) (collect intermediate-a (copy-sequence a)) (collect intermediate-b (copy-sequence b)) (finally-return a b intermediate-a intermediate-b))
Next: Control Flow, Previous: Accumulation, Up: Loop Commands [Contents][Index]
Boolean commands are used to test whether a condition holds true
during the loop. They work like a combination of accumulation commands
(Accumulation) and early-exit commands (Early Exit), in
that values are by default stored in loopy-result
and that they can terminate
the loop without forcing a return value.
Note: Due to how the commands work, there are restrictions to how their target variables can be used. First, the ‘always’ and ‘never’ commands must use the same variable to work together correctly. Second, using the command ‘thereis’ with the same variable as ‘always’ (and/or ‘never’) is an error, as this would create conflicting initial values for the implicit return value.
Check the result of the condition ‘EXPR’.
If the condition evaluates to nil
, end the loop. If the command was run,
return the value of the condition via ‘VAR’. Otherwise, if the command was
never run, return t
via ‘VAR’.
The steps are thus:
loopy-result
) is initially bound to t
, using
it as the implicit return value of the loop.
nil
, the loop is exited.
t
.
;; => t (loopy (list i '(1 0 1 0 1)) (always (< i 2))) ;; Returns the final value of the condition: ;; => 5 (loopy (list i '(1 0 1 0 1)) (always (and (< i 2) 5))) ;; => nil (loopy (list i '(1 0 1 0 1)) (always (< i 1))) ;; NOTE: Here, the implicit return value is `t' because an ;; `always' command was used, and that return value ;; is never updated to "hello" because the `always' ;; command is never actually run. ;; ;; => t (loopy (list i '(1 1 1 1)) (when nil (always (and (> i 5) "hello"))))
Check the condition ‘EXPR’. If the
condition is ever non-nil
, then the loop is exited and returns nil
via
‘VAR’. Otherwise the loop returns t
via ‘VAR’.
The steps are thus:
loopy-result
) is initialized to t
and used as
the loop’s implicit return value.
nil
, then the variable is set to nil
and the loop is exited.
Note: Unlike the ‘always’ command, ‘never’ does not store any information in the variable until it ends the loop. Therefore, ‘never’ does not affect the loop’s implicit return value when using the ‘always’ command so long as the conditions of ‘never’ are always
nil
.Be aware, though, that this behavior depends on ‘always’ and ‘never’ using the same variable.
;; => t (loopy (list i '(1 0 1 0 1)) (never (= i 3))) ;; => nil (loopy (list i '(1 0 1 0 1)) (never (= i 0))) ;; This example taken from the documentation of CL's Iterate package. ;; ;; => 2, not t (loopy (cycle 2) (always 2) (never nil))
Check the result of the condition ‘EXPR’.
If the condition evaluates to a non-nil
value, the loop returns that value
via ‘VAR’. Otherwise, the loop returns nil
via ‘VAR’.
The steps are thus:
loopy-result
) is initialized to nil
and used
as the implicit return value of the loop.
nil
, the loop exits.
;; => 3 (loopy (list i '(1 0 1 3 1)) ;; Note: `and' returns the last value it evaluates. (thereis (and (> i 2) i))) ;; => nil (loopy (list i '(1 0 1 0 1)) (thereis (and (> i 2) i))) ;; => 7 (loopy (list i '(nil nil 3 nil)) (thereis i) (finally-return (+ loopy-result 4)))
Next: Sub-Loops, Previous: Checking Conditions, Up: Loop Commands [Contents][Index]
Next: Skipping Cycles, Up: Control Flow [Contents][Index]
Conditional commands in loopy
can take multiple sub-commands, and work like
their Lisp counterparts. There is therefore no need for an ‘and’ command as
used in cl-loop
.
Run the commands ‘CMDS’ following the first
non-nil condition ‘EXPR’. This is the loopy
version of the cond
special
form from normal Emacs Lisp.
;; => '((2 4 6) (1 3 5) ("cat" "dog")) (loopy (list i '(1 2 3 "cat" 4 5 6 "dog")) (cond ((not (numberp i)) (collect not-numbers i)) ((cl-evenp i) (collect evens i)) (t (collect odds i))) (finally-return evens odds not-numbers))
Run the first command if ‘EXPR’ is non-nil. Otherwise,
run the remaining commands. This is the loopy
version of the if
special
form from normal Emacs Lisp.
;; => '((1 3 5 7) (2 4 6) (3 3 3)) (loopy (sequence i [1 2 3 4 5 6 7]) (if (cl-oddp i) (collect odds i) (collect evens i) (collect some-threes 3)) (finally-return odds evens some-threes))
Run ‘CMDS’ only if ‘EXPR’ is non-nil. This is the
loopy
version of the when
macro from normal Emacs Lisp.
;; Get only the inner lists with all even numbers: ;; ;; => '((2 4 6) (8 10 12) (16 18 20)) (loopy (list i '((2 4 6) (8 10 12) (13 14 15) (16 18 20))) (when (loopy (list j i) (always (cl-evenp j))) (collect only-evens i)) (finally-return only-evens))
Run ‘CMDS’ only if ‘EXPR’ is nil. This is the loopy
version of the unless
macro from normal Emacs Lisp.
;; Get only the inner lists with all even numbers: ;; ;; => '((2 4 6) (8 10 12) (16 18 20)) (loopy (list i '((2 4 6) (8 10 12) (13 14 15) (16 18 20))) (unless (loopy (list j i) (thereis (cl-oddp j))) (collect only-evens i)) (finally-return only-evens))
Next: Early Exit, Previous: Conditionals, Up: Control Flow [Contents][Index]
Skip the remaining commands and continue to the next loop iteration.
This command also has the aliases ‘skipping’ and ‘continuing’.
;; => (2 4 6 8 10 12 14 16 18 20) (loopy (sequence i (number-sequence 1 20)) (when (cl-oddp i) (skip)) (collect i))
Skip the remaining commands and continue to the next loop iteration of the loop ‘NAME’.
This command also has the aliases ‘skipping-from’ and ‘continuing-from’.
;; => ((1 2 3) (7 8 9)) (loopy outer (array i [(1 2 3) (4 5 6) (7 8 9)]) (loopy (list j i) (when (= 5 j) (skip-from outer))) (collect i))
Previous: Skipping Cycles, Up: Control Flow [Contents][Index]
The loop is contained in a cl-block
, which can be exited by the function
cl-return-from
. Indeed, the ‘return’ and ‘return-from’ commands described
below are just wrappers around that function.
As with the ‘finally-return’ special macro argument, passing multiple return
values to those commands will return a list of those values. If no value is
given, nil
is returned.
In Loopy, implied accumulation variables can be modified a final time after the loop exits in order to finalize their values. For example, if an accumulated list is built in reverse, then it will be reversed into the correct order after the loop completes. Loopy has “return” commands for (1) immediately returning a value from the loop without finalizing values and “leave” commands for (2) leaving the loop without forcing a return value, allowing values to be finalized.
;; An example of not finalizing the accumulated value: ;; ;; => (4 3 2 1 0) (loopy (numbers i :to 10) (if (< i 5) (collect i) ;; Not finalized: (return loopy-result))) ;; An example of finalizing the accumulated value: ;; ;; => (0 1 2 3 4) (loopy (numbers i :to 10) (if (< i 5) (collect i) (leave)) (finally-return loopy-result))
As noted in Special Macro Arguments, the special macro argument ‘finally-do’ does not affect the return value of the loop.
;; => (0 1 2 3 4) (loopy (numbers i :to 10) (if (< i 5) (collect i) (leave)) (finally-do 7)) ;; => (4 3 2 1 0) (loopy (numbers i :to 10) (if (< i 5) (collect i) ;; Not finalized: (return loopy-result)) (finally-do (cl-callf2 cons 22 loopy-result)))
As noted in Special Macro Arguments, the special macro argument ‘finally-return’ overrides the return value of the loop, including values that would have been returned by any ‘return’ commands.
;; => 22 (loopy (numbers i :to 10) (if (< i 5) (collect i) ;; Not finalized: (return loopy-result)) (finally-return 22)) ;; => 22 (loopy (numbers i :to 10) (if (< i 5) (collect i) (leave)) (finally-return 22))
Leave the current loop without forcing a return value.
This command also has the alias ‘leaving’.
;; => (1 2 3 4) (loopy (list i '(1 2 3 4 5 6 7)) (if (= i 5) (leave) (collect i)))
Leave the loop ‘NAME’ without forcing a return value. This command is equivalent to ‘(at NAME (leave))’ (Sub-Loops).
This command also has the alias ‘leaving-from’.
;; => ([2 4] [6 8]) (loopy outer (list i '([2 4] [6 8] [7 10])) (loopy (array j i) (when (cl-oddp j) ;; Equivalent to `(at outer (leave))' (leave-from outer))) (collect i))
Return from the current loop without finalizing values, returning ‘[EXPRS]’.
This command also has the alias ‘returning’.
;; => 6 (loopy (with (j 0)) (do (cl-incf j)) (when (> j 5) (return j)))
Return from the loop ‘NAME’, returning ‘[EXPRS]’. This command is equivalent to ‘(at NAME (return))’ (Sub-Loops).
This command also has the alias ‘returning-from’.
;; => 'bad-val? (loopy (named outer-loop) (list inner-list '((1 2 3) (1 bad-val? 1) (4 5 6))) (loopy (list i inner-list) (when (eq i 'bad-val?) (return-from outer-loop 'bad-val?))))
Leave the loop once ‘COND’ is false, without forcing a return value. ‘(while COND)’ is the same as ‘(until (not COND))’.
;; => (1 2 3 4) (loopy (list i '(1 2 3 4 5 6 7)) (while (/= i 5)) (collect i)) ;; Same as the above: ;; ;; => (1 2 3 4) (loopy (list i '(1 2 3 4 5 6 7)) (unless (/= i 5) (leave)) (collect i))
Leave the loop once ‘COND’ is true, without forcing a return value. ‘(until COND)’ is the same as ‘(while (not COND))’.
;; => (1 2 3 4) (loopy (list i '(1 2 3 4 5 6 7)) (until (= i 5)) (collect i)) ;; Same as the above: ;; ;; => (1 2 3 4) (loopy (list i '(1 2 3 4 5 6 7)) (when (= i 5) (leave)) (collect i))
Previous: Control Flow, Up: Loop Commands [Contents][Index]
Loopy provides two sets of commands for working with sub-loops:
In the example below, the arguments of the ‘do’ command are inserted into the loop body literally, so by the time the inner loop expands, the outer loop has already been expanded into normal Emacs Lisp code, and so the inner macro cannot find any outer Loopy loop named “outer”.
;; Can signal an error or not work as expected: (loopy (named outer) (list i '((1 2) (3 4) (5 6))) (do (loopy (list j i) (when (= j 5) (leave-from outer)))) (collect i))
In general, correct code requires that inner loops be expanded during the expansion of the outer loop, as done with other commands.
;; Works as expected: ;; ;; => ((1 2) (3 4)) (loopy outer (list i '((1 2) (3 4) (5 6))) (loopy (list j i) (when (= j 5) (leave-from outer))) (collect i))
Warning: Don’t confuse using these sub-loop commands with using calls to the macros
loopy
andloopy-iter
. For example, the ‘EXPR’ parameter to loop commands is used literally, and is not guaranteed to be able to affect macro expansion.
The commands are described in more detail below.
Use the loopy
macro as a
command.
;; => (1 11 2 12 3 13 4 14) (loopy outer (list i '([1 2] [3 4] 'bad [5 6] [7 8])) (loopy (unless (arrayp i) (leave-from outer)) (array j i) (at outer (collect j) (collect (+ j 10)))))
Use the
loopy-iter
macro as a command (The loopy-iter
Macro).
This feature can only be used after first loading the library ‘loopy-iter’.
(require 'loopy-iter) ;; => (1 11 2 12 3 13 4 14) (loopy outer (list i '([1 2] [3 4] 'bad [5 6] [7 8])) (loopy-iter (unless (arrayp i) (leaving-from outer)) (arraying j i) (cl-flet ((10+ (x) (+ x 10))) (at outer (collecting j) (collecting (10+ j))))))
Parse commands with respect to ‘LOOP-NAME’. For example, a ‘leave’ subcommand would exit the loop ‘LOOP-NAME’, and an accumulation command would create a variable in that super-loop.
If one did not use ‘at’ in the below example, then the accumulation would be
local to the sub-loop and the return value of the loop ‘outer’ would be nil
.
;; => (4 5 10 11 16 17) (loopy outer (array i [(1 2) (3 4) (5 6)]) (loopy (with (sum (apply #'+ i))) (list j i) (at outer (collect (+ sum j)))))
Keep in mind that the effects of flags (Using Flags) are local to the loops in which they are used, even when using the ‘at’ command.
;; => ((1 2 11 12) ;; ((2) (3) (12) (13))) (loopy outer (flag pcase) (array elem [(1 2) (11 12)]) (collect `(,first . ,rest) elem) ;; NOTE: The sub-loop uses the default destructuring style. ;; The `pcase' style only affects the surrounding loop. (loopy (at outer (collect (first &rest rest) (mapcar #'1+ elem))) (leave)) (finally-return first rest))
Next: The loopy-iter
Macro, Previous: Loop Commands, Up: Loopy: A Looping and Iteration Macro [Contents][Index]
The below macros make Loopy’s built-in destructuring system available for
general use (Basic Destructuring). For example, loopy-let*
can be used for
let
-binding destructured values, similar to pcase-let*
. Because libraries
like ‘pcase’ and ‘seq’ already provide similar destructuring macros using their
own destructuring systems, these macros are not affected by the flags
that configure the destructuring used by loop commands (Using Flags), as that would
be redundant.
To be clear, these destructuring macros can be used outside of the looping macros.
;; => (5 9) (loopy (flag seq) ;; `seq-let'-destructuring used by loop command: (list (_ &rest cdr) '((key1 . (2 . 3)) (key2 . (4 . 5)))) ;; Doesn't use `seq-let'-style destructuring: (collect (loopy-let* (((a . b) cdr)) (+ a b))))
loopy-let*
Use destructuring in a let
form, like in pcase-let*
and
seq-let
.
;; => (1 2 3 4 5 28) (loopy-let* ((a 1) ([b c] [2 3]) ((&keys k1 k2 (k3 28)) '(:k1 4 :k2 5))) (list a b c k1 k2 k3))
loopy-setq
Use destructuring in a setq
form, like in seq-setq
and
pcase-setq
.
;; => (1 2 3 4 5 28) (let (a b c k1 k2 k3) (loopy-setq a 1 [b c] [2 3] (&keys k1 k2 (k3 28)) '(:k1 4 :k2 5)) (list a b c k1 k2 k3))
loopy-lambda
Use destructuring in a lambda
’s argument list, like in
pcase-lambda
and cl-function
.
;; => ((1 2 :k1 3) 110) (funcall (loopy-lambda ((&whole first-arg a b &key k1 (k2 4)) second-arg) (list first-arg (+ a b k1 k2 second-arg))) (list 1 2 :k1 3) 100)
loopy-ref
Create destructured references to the fields in a sequence via
cl-symbol-macrolet
. Do not confuse this with the behavior of cl-letf
,
which temporarily binds those places to a value.
This macro uses the destructuring found in the sequence reference iteration commands (Sequence Reference Iteration). There are some limitations to this functionality in Emacs Lisp, which are described in that section.
;; => ((20 2 23) [24 25 26]) (let ((l1 (list 1 2 3)) (a1 (vector 4 5 6))) (loopy-ref (((a _ b) l1) ([c &rest d] a1)) (setf a 20 b 23 c 24 d [25 26])) (list l1 a1))
Next: Using Flags, Previous: Destructuring Macros, Up: Loopy: A Looping and Iteration Macro [Contents][Index]
loopy-iter
Macroloopy-iter
is a macro that allows the embedding of loop commands inside of
arbitrary code. This is different from the loop command ‘do’, which allows the
embedding of arbitrary code inside of a loopy
loop. You must use require
to
load this feature.
This macro is named after the iterate
or iter
macro provided by the Common
Lisp package “Iterate” 3 (not to be confused with the iter-*
functions
provided by Emacs). However, while loopy
and loopy-iter
were influenced by
iterate
, loopy-iter
is not a port of iterate
to Emacs Lisp.
(require 'loopy-iter) ; <- Must `require' to load feature. ;; => ((1 2 3) (-3 -2 -1) (0)) (loopy-iter (accum-opt positives negatives other) (numbering i :from -3 :to 3) (pcase i ((pred cl-plusp) (collecting positives i)) ((pred cl-minusp) (collecting negatives i)) (_ (collecting other i))) (finally-return positives negatives other)) ;; => (1 2 3) (loopy-iter (listing elem '(1 2 3)) (funcall #'(lambda (x) (collecting x)) elem))
The arguments of loopy
are limited to loop commands and special macro
arguments. loopy-iter
differs by allowing arbitrary Lisp expressions, in
which loop commands are treated as macros to be expanded by macroexpand-all
.
Hence, a loop command could overshadow the function value of a symbol. There
are two ways to avoid such conflicts.
The first way is to use non-conflicting aliases. Like in Iterate (and
cl-loop
, to an extent), almost all commands in loopy
have aliases in the
present-participle form (the “-ing” form). For example, Loopy provides the
command ‘list’ with the alias ‘listing’. Because the command name ‘list’ would
conflict with the built-in Emacs Lisp function list
, only the command name
‘listing’ is supported by default. These names of commands and special macro
arguments are called bare names to distinguish them from the second
way of avoiding conflicts. The complete list of commands and special macro
arguments that are recognized by default are given in Default Bare Names in loopy-iter
.
;; In `loopy', `list' is unambiguously a command name. ;; => (1 2 3 4) (loopy (named outer) (list i '((1 2) (3 4))) (loop (list j i) (at outer (collect j)))) ;; In `loopy-iter', `list' would be a function. `listing' is the command. ;; => (1 2 3 4) (loopy-iter (named outer) (listing i (list (list 1 2) (list 3 4))) (loopy-iter (listing j i) ;; Can use `at' instead of `atting': (at outer (collecting j))))
The command aliases recognized by loopy-iter
can be customized with the user
option loopy-iter-bare-commands
, which is a list of symbols naming commands
and their aliases. Again, these commands are found in the loop body by using
Emacs Lisp’s macro-expansion features, so adding an alias that overrides a
symbol’s function definition can cause errors. loopy
, whose environment is
more limited, does not have this restriction.
The special macro arguments (and their aliases) recognized by loopy-iter
can
be set in the user option loopy-iter-bare-special-macro-arguments
. Some of
their built-in aliases, such as ‘let*’ for ‘with’, are excluded by default.
The first method above deals with looping features that are written like
functions. If for some reason a suitable alias cannot be used or cannot be
added to one of the above user options, one can write the feature name preceded
by one of the keywords in loopy-iter-keywords
. This is the second method.
By default, the possible keywords are ‘for’, ‘accum’, ‘exit’, and ‘arg’. These
symbols do not share a name with any built-in Emacs feature and are similar to
the keywords used by other packages. Note that these are not Lisp “keywords”
that are prefixed with a colon, such as the ‘:test’ in (cl-count ITEM SEQ :test
FUNC)
. For example,
let*
special form.
This method recognizes all commands and their aliases in the user option
loopy-aliases
.
;; => ((1 2 3) (-3 -2 -1) (0)) (loopy-iter (arg accum-opt positives negatives other) (for numbers i :from -3 :to 3) (pcase i ((pred cl-plusp) (accum collect positives i)) ((pred cl-minusp) (accum collect negatives i)) (_ (accum collect other i))) (arg finally-return positives negatives other))
Listing 6.1: The first example, but now using keyword symbols.
While the symbols ‘for’, ‘accum’, ‘exit’, and ‘arg’ are named for iteration,
accumulation, early exits, and special macro arguments, respectively, any
keyword in the user option loopy-iter-keywords
can be used to identify any
loop command or special macro argument. For example, ‘(accum collect VAL)’ and
‘(for collect VAL)’ are both valid ways of referring to the ‘collect’ loop command
in loopy-iter
. Instead of ‘(arg let* (VAR 7))’ in the example above, one could
also write ‘(exit let* (VAR 7))’ if one really wanted to.
While loopy-iter
imposes less restrictions on the contents of the loop body
than the macro loopy
, restrictions on the placement of loop commands and
special macro arguments still apply in loopy-iter
. For example, iteration
commands must still occur at the top level of loopy-iter
or a sub-loop.
;; BAD: (loopy-iter (let ((a (progn ;; ERROR: `listing' must occur at the top level. (listing j '(8 9 10 11 12)) j))) (collecting a))) ;; GOOD: ;; => (8 9 10 11 12) (loopy-iter (let ((a (progn ;; NOTE: No restriction on placement of `setting'. (setting j 8 (1+ j)) (when (> j 12) (leaving)) j))) (collecting a)))
In the macro loopy
, the commands ‘loopy’ and ‘loopy-iter’ are needed to
correctly handle sub-loops. Those commands are not needed in the macro
loopy-iter
, since the macro expands any macros in its argument while
processing them.
;; => (2 3 4 5) (loopy-iter outer (listing i '([1 2] [3 4])) ;; NOTE: `loopy-iter' macro, not command (loopy-iter (arraying j i) (at outer (let ((val (1+ j))) (collecting val))))) ;; => (2 3 4 5) (loopy-iter outer (listing i '([1 2] [3 4])) ;; NOTE: `loopy' macro, not command (loopy (array j i) (set val (1+ j)) (at outer (collect val))))
Finally, there are a few things to keep in mind when using loopy-iter
:
loopy-iter
is expanding its arguments. For example,
cl-return-from
is known to be problematic, since it tries to interact with
the correct cl-block
.
Macros that should not be expanded while loopy-iter
expands are listed in
loopy-iter-suppressed-macros
. Note that this suppression is only in effect
while loopy-iter
expands its loop commands. Once loopy-iter
outputs its
code, Emacs will attempt to further expand any macros in the outputted code.
Ideally, such problematic macros are uncommon. Please report such cases on
this project’s issues tracker so that they can be added to
loopy-iter-suppressed-macros
by default.
Up: The loopy-iter
Macro [Contents][Index]
loopy-iter
This section lists the default aliases supported as bare names in the macro
loopy-iter
. The list of supported bare names can be customized in the user
options loopy-iter-bare-commands
and
loopy-iter-bare-special-macro-arguments
.
By default, the following commands are not recognized:
loopy-iter
expands macros
in its arguments, and so can properly handle instances of itself and the macro
loopy
without needing them to be reimplemented as loop commands.
while
.
Use (when (not COND) (leaving))
or (unless COND (leaving))
instead.
Using the commands ‘returning’ and ‘returning-from’ are the same as using the
macros cl-return
and cl-return-from
, except that the commands automatically
create a list if more than one return value is given.
;; => (6 7 8) (loopy-iter (numbering n :from 0 :to 10) (when (> n 5) (returning n (1+ n) (+ 2 n)))) ;; => (6 7 8) (loopy-iter (numbering n :from 0 :to 10) (when (> n 5) (cl-return (list n (1+ n) (+ 2 n)))))
Next: Custom Aliases, Previous: The loopy-iter
Macro, Up: Loopy: A Looping and Iteration Macro [Contents][Index]
A flag is a symbol passed to the ‘flag’ or ‘flags’ special macro
argument, changing the macro’s behavior. Currently, flags affect what method
loopy
uses to perform destructuring (‘pcase’, ‘seq’, ‘dash’, or the default)
Flags are applied in order. If you specify ‘(flags seq pcase)’, then loopy
will use pcase-let
for destructuring, not seq-let
.
If you wish to always use a flag, you can add that flag to the list
loopy-default-flags
. These can be overridden by any flag given in the ‘flag’
special macro argument.
The following flags are currently supported:
Use pcase-let
for destructuring
((elisp)Destructuring with pcase Patterns).
Use seq-let
for destructuring ((elisp)seq-let).
Use the style of destructuring found in the ‘dash’ library ((dash)-let).
Use the default behavior for all options.
For convenience, all flags (except ‘default’) can be undone by prefixing them
with ‘-’ (a dash or minus sign), which reverts loopy
to its default behavior.
For example, if you have set loopy-default-flags
to ‘(dash)’ and wish to use
the default destructuring method, you can use ‘(flags default)’ or ‘(flags
-dash)’. These prefixed flags only apply when the unprefixed version is active.
That is, ‘(flags pcase -dash)’ is the same as just ‘(flags pcase)’, regardless
of the value of loopy-default-flags
, as ‘pcase’ destructuring will override
all uses of ‘dash’ destructuring as it comes later in the list. Similarly,
‘(flags -dash dash)’ and ‘(flags -dash +dash)’ leave ‘dash’ destructuring
enabled, and ‘(flags +dash -dash)’ disables ‘dash’ destructuring and uses the
default behavior.
The destructuring flags (‘pcase’, ‘seq’, and ‘dash’) are separate libraries (respectively, ‘loopy-pcase’, ‘loopy-seq’, and ‘loopy-dash’) that must be loaded after ‘loopy’. Currently, ‘loopy-dash’ is a separate package.
Below are some example of using the destructuring flags. These flags affect the destructuring of:
These flags do not affect the destructuring of generalized variables
(setf
-able places) as the libraries ‘pcase.el’, ‘seq.el’, and ‘dash.el’ do not
yet provide the required functionality.
;; => ((1 4) coll1 ;; ((2 3) (5 6)) whole ;; (2 5) x ;; (3 6)) y (require 'loopy-dash) (loopy (flag dash) (list (i j) '((1 (2 3)) (4 (5 6)))) (collect coll1 i) (collect (whole &as x y) j) (finally-return coll1 whole x y)) ;; => ((1 4) (3 6) 10 20 nil nil) (require 'loopy-pcase) (loopy (flag pcase) (with ((or `[,v1 ,v2] `(,v3 ,v4)) [10 20])) (list elem '((1 (2 3)) (4 (5 6)))) (collect `(,a (,_ ,b)) elem) (finally-return a b v1 v2 v3 v4)) ;; => (14 26) (require 'loopy-seq) (loopy (flag seq) (with ([v1 v2] [10 20])) (list (i &rest j) '((1 . 2) (3 . 4))) (sum sum1 i) (sum sum2 j) (finally-return (+ sum1 v1) (+ sum2 v2)))
Warning: For accumulation commands, there is no guarantee that a variable that was used in destructuring was meant to be user-facing. Destructuring systems can create new variables as they please, which can be interpreted as accumulation variables.
Consider the below example in which a hypothetical pcase
pattern creates the
variable temporary?
for destructuring. Loopy has no way of knowing whether it
was the user who create the variable, or the destructuring system. As a result,
temporary?
is treated as an accumulation variable. Such cases can be unwanted
and produce inefficient code.
;; Possibly unexpected behavior: ;; ;; => ((1 2 3) (2 4 6)) (loopy (flag +pcase) (list i '(1 2 3)) (collect (and whole (let temporary? (* 2 whole))) i) (finally-return whole temporary?))
Next: Custom Commands, Previous: Using Flags, Up: Loopy: A Looping and Iteration Macro [Contents][Index]
An alias is another name for a command or special macro argument.
loopy
comes with several built-in aliases, such as ‘string’ for the command
‘array’ or ‘else’ for the special macro argument ‘after-do’.
Command or Special Macro Argument | Built-In Aliases |
---|---|
‘array’ | ‘string’ |
‘seq-ref’ | ‘sequence-ref’, ‘seqf’ |
‘after-do’ | ‘after’, ‘else’, ‘else-do’ |
An alias works the same as the original command or special macro argument. They are provided for clarity and convenience.
;; => ("a" "b" "c" "d") (loopy (array i "abcd") (collect (char-to-string i))) ;; => ("a" "b" "c" "d") (loopy (string i "abcd") (collect (char-to-string i)))
Users can define custom aliases using the macro loopy-defalias
, which takes an
alias and a definition as arguments. These arguments can be quoted or unquoted.
(loopy-defalias items array) ;; => (1 2 3) (loopy (items i [1 2 3]) (collect i))
The definition must exist for the alias to be defined correctly. Definitions can themselves be aliases, so long as they are already defined. In other words, when aliasing custom commands, you should define the alias after defining the command (Custom Commands).
;; Define an alias for the `items' alias from above: (loopy-defalias items2 items) ;; => (1 2 3) (loopy (items2 i [1 2 3]) (collect i))
When looking for how to parse a command, loopy
will check aliases before
checking the true names of commands. Effectively, this means that commands can
be overridden by aliases, though this is discouraged. Such commands can still
be accessed via their other names.
;; Define `cons' as an alias of `array': (loopy-defalias cons array) ;; => (1 2 3) (loopy (cons i [1 2 3]) (collect i)) ;; ERROR: Can no longer use the original definition: (loopy (cons i '(1 2 3)) (collect i)) ;; Other names still work: ;; => ((1 2 3) (2 3) (3)) (loopy (conses i '(1 2 3)) (collect i))
Special macro arguments (Special Macro Arguments) can also be aliased. Using an alias does not change the fact that the special macro arguments are parsed before loop commands.
(loopy-defalias as with) ;; => (8 9 10) (loopy (as (a 7)) (list i '(1 2 3)) (collect (+ i 7)))
The macro loopy-defalias
modifies the user option loopy-aliases
. However,
while loopy
is still changing, it is recommended to avoid modifying this
variable directly, as its structure may change in the future. loopy-defalias
is the forward-compatible way of creating aliases.
Next: Comparing to cl-loop
, Previous: Custom Aliases, Up: Loopy: A Looping and Iteration Macro [Contents][Index]
This section contains information about how loop commands work and how one can
add custom commands to loopy
. Two examples are provided.
always
Commandloopy-iter
macroNext: Hello World, Up: Custom Commands [Contents][Index]
The core working of loopy
is taking a loop command and generating code that
becomes part of a while
-loop. This code is represented by
instructions, which basically describe where and how code is inserted
into and around a template of a while
-loop.
Some examples of instructions are:
let
-like form to make sure it’s locally
scoped.
let
-like form to contain a given value.
while
-loop.
This location is referred to as the main body of the loop.
For example, parsing the command ‘(list i '(1 2 3))’ produces the following list of instructions. Some commands require the creation of unique temporary variables, such as ‘list-211’ in the below output.
((loopy--iteration-vars (list-211 '(1 2 3))) (loopy--iteration-vars (i nil)) (loopy--pre-conditions (consp list-211)) (loopy--main-body (setq i (car list-211))) (loopy--latter-body (setq list-211 (cdr list-211))))
The first element of an instruction describes where to insert code into the
template. The second element of an instruction is the inserted code. You can
see that not all of the code to be inserted is a valid Lisp form. For example,
the above instruction referencing loopy--iteration-vars
inserts a binding for
the variable ‘list-211’ into a let
-like form.
Place | Code |
---|---|
‘loopy--iteration-vars’ | ‘(list-211 '(1 2 3))’ |
‘loopy--latter-body’ | ‘(setq list-211 (cdr list-211))’ |
‘loopy--pre-conditions’ | ‘(consp list-211)’ |
‘loopy--iteration-vars’ | ‘(i nil)’ |
‘loopy--main-body’ | ‘(setq i (car list-211)))’ |
Instructions are applied in order in such a way that earlier instructions are
not overridden by later instructions. For example, if the special macro
argument ‘with’ sets a variable’s value, that value will not be overridden by
commands which might try to initialize that variable to nil
. This works
because special macro arguments are always parsed before loop commands.
Commands are parsed by loopy--parse-loop-command
, which receives a command
call, such as ‘(list i '(1 2 3))’, and returns a list of instructions. It does
this by searching for an appropriate command-specific parsing function in
loopy-aliases
and ultimately in loopy-command-parsers
. For parsing multiple
commands in order, there is loopy--parse-loop-commands
, which wraps the
single-command version.
For example, consider the function loopy--parse-if-command
, which parses the
‘if’ loop command. It needs to check the instructions of the sub-commands
passed to ‘if’, looking for code that would be inserted into the loop’s main
body (as determined by the first element of the instruction). Once found, it
wraps that code with an if
-form.
;; Example instructions that could be generated by "set" and "if" commands: ;; ;; => ((loopy--other-vars (i nil)) ;; (loopy--main-body (setq i 1))) (loopy--parse-loop-command '(set i 1)) ;; => ((loopy--other-vars (i nil)) ;; (loopy--main-body (if (my-condition) ;; (setq i 1) ;; (setq i 2)))) (loopy--parse-if-command '(if (my-condition) (set i 1) (set i 2)))
For the purpose of this example, below is a version of the parsing function made of the basic Lisp features with which you are familiar. The actual definition makes use of more convenient Emacs Lisp libraries and can be seen in the library loopy-commands.el.
(require 'loopy) (defun loopy--parse-if-command (arg) "Parse the `if' loop command usage ARG. ARG is of the form (if CONDITION IF-TRUE &rest IF-FALSE)." (let ((condition (cadr arg)) ; Second element of `arg'. (if-true (caddr arg)) ; Third element of `arg'. (if-false (cdddr arg))) ; Remaining elements of `arg'. ;; The main processing of this function is to separate instructions ;; for the loop's main body from other instructions, ;; and to then wrap those main-body instructions with an ;; `if' special form. (let ((full-instructions) (if-true-main-body) (if-false-main-body) ;; This variable is just so that iteration commands know when ;; they are being used away from the top level of the loop's ;; structure (which is an error). (loopy--in-sub-level t)) ;; Process the instructions for the command that should run if the ;; condition is true. (dolist (instruction (loopy--parse-loop-command if-true)) (if (eq 'loopy--main-body (car instruction)) (push (cadr instruction) if-true-main-body) (push instruction full-instructions))) ;; Process the instructions for the commands that should run ;; if the condition is false. (dolist (instruction (loopy--parse-loop-commands if-false)) (if (eq 'loopy--main-body (car instruction)) (push (cadr instruction) if-false-main-body) (push instruction full-instructions))) ;; Note: `push' adds elements to the front of a list, ;; so we need to reverse these lists before returning ;; the new list of instructions. ;; `loopy--parse-loop-command' always returns a list of instructions. ;; For some commands, that means wrapping multiple instructions in ;; a `progn' form. For others, we need to extract the only element. (setq if-true-main-body (if (= 1 (length if-true-main-body)) (car if-true-main-body) (cons 'progn (nreverse if-true-main-body)))) ;; Return the new, full list of instructions. (cons `(loopy--main-body . (if ,condition ,if-true-main-body ,@(nreverse if-false-main-body))) (nreverse full-instructions)))))
In general, the code of command parsers is not complicated. The hardest part of
writing the parser is making sure that the code inserted into the while
-loop
template ends up in the correct order.
A loop command has 7 main places to put code:
Lists of a symbol and a macro expansion that will
be given to ‘cl-symbol-macrolet’. This is used to create named setf
-able
places. The expansion you use depends on the kind of sequence and how the it
is updated.
For example, ‘(list-ref i my-list)’ declares ‘i’ to be a symbol which expands to ‘(car TEMP-VAR)’, in which ‘TEMP-VAR’ holds the value of ‘my-list’. At the end of the loop body, ‘TEMP-VAR’ is set to its ‘cdr’, ensuring that the next call to ‘car’ returns the correct value.
Lists of a symbol and an expression that will be
given to let*
. This is used for initializing variables needed for iteration
commands, such as the ‘i’ in ‘(list i '(1 2 3))’ or to store the list ‘'(1 2
3)’ in ‘(list i '(1 2 3))’. This also includes variables needed for
destructuring for said commands.
Loopy will signal an error if iteration variables would be initialized multiple times. For example, such as if the variable was used by multiple iteration commands, that could result in expanding into incorrect code which would fail during runtime
Lists of a symbol and an expression that will be
given to let*
. This is used for initializing variables needed for
accumulation commands, such as the ‘coll’ in ‘(collect coll my-val)’ or any
variables needed for destructuring for said commands.
Lists of a symbol and an expression that will be
given to let*
. This is used for initializing variables needed for
generic commands, such as the ‘my-var’ in ‘(set my-var 2)’ or any
variables needed for destructuring for said command.
Expressions that determine if the ‘while’ loop
runs/continues, such as whether a list still has elements in it. If there is
more than one expression, than all expressions are used in an and
special
form.
Expressions that make up the main body of the loop. The
“main” body of the loop is similar in use to the arguments given to, for
example, the dolist
macro. Code that can be placed in the main body
includes:
Expressions that need to be run after the main body,
such as updating some of the variables that determine when a loop ends. For
example, for the command ‘(list i my-list)’, the command applies the function
that moves the command through the list (by default, cdr
) in the loop’s
latter body, so that during the next iteration of the main body the iteration
variable ‘i’ can be set to the car
of that cdr
.
Expressions that determine whether the ‘while’ loop continues, but checked after the loop body has run. The code from this is ultimately appended to the latter body before being substituted in.
For accumulation commands, you might also wish to place values in the following:
A list of values to be returned by the loop if no other return value is specified/reached. A value is added to this list when an accumulation command does not specify an accumulation variable, and in some special other cases.
By default, the implicit return value is loopy-result
, and so this variable
is usually just a list of the symbol ‘loopy-result’.
Actions to perform on variables (usually accumulation variables) after the loop ends. Some implied accumulation commands need to update the variable one final time after ending the loop. Some examples are:
Each accumulation variable can only be updated once, in a single way. For example, a variable cannot be reversed according to the needs of one command and then coerced into a new type according to the needs of another. Commands acting on the same accumulation variable must require the same final update, including if they require no final update.
Loopy will attempt to produce efficient code, and will not attempt to set up features which are not used. Therefore, the expanded code depends on the kinds of instructions that are returned by the parsing functions. For the most part, the instructions affect the expansion of the loop that contains their respective command. However, there are cases where a command must send instructions to a surrounding loop, not just the loop which immediately contains it. Consider using an accumulation command within the ‘at’ command, as in the below example. The accumulation variable must be declared for the loop ‘outer’, but the accumulation itself must still occur within the loop ‘inner’.
;; => (1 2 3 4) (loopy (named outer) (array i [(1 2) (3 4)]) (loopy inner (list j i) (at outer (collect coll j))) (finally-return coll))
To communicate these instructions, use loopy--at-instructions
. For example,
the output of parsing ‘(at outer (collect j :at start))’ would be a list of
instructions similar to those below. While all of these sub-instructions are
produced by parsing the ‘collect’ command, not all are sent to the loop ‘outer’.
;; Example instructions from parsing an `at` command. ((loopy--at-instructions (outer (loopy--accumulation-vars (loopy-result nil)) (loopy--implicit-return loopy-result))) (loopy--main-body (setq loopy-result (cons j loopy-result))))
Instructions that should be interpreted by a surrounding loop. For example, this kind of instruction is used by the ‘at’, ‘skip-from’, and ‘leave-from’ commands. The instruction’s value is a list of a loop name followed by sub-instructions.
This variable works as a something like a combination of a stack and a map. This means that then when multiple surrounding loops share the same name, the instructions affect the innermost surrounding loop of that name.
There are 4 more variables a loop command can push to, but they are derived from the macro’s arguments. Adding to them after using a macro argument might lead to unintended behavior. You might wish to use them if, for example, you are concerned with what happens after the loop exits/completes.
Expressions to evaluate before the loop. These are derived from the ‘before-do’ macro argument.
Expressions to evaluate after the loop completes successfully. These are derived from the ‘after-do’ macro argument.
Expressions to evaluate after the loop completes, regardless of success. These are derived from the ‘finally-do’ macro argument.
An expression that is always returned by the macro, regardless of any early returns in the loop body. This is derived from the ‘finally-return’ macro argument.
Some commands might depend on the name of the loop. The symbol which names the
loop is stored in the variable loopy--loop-name
. The default name is nil
.
The structure of the macro’s expanded code depends on the features used (for
example, loopy
won’t try to declare variables if none exist), but the result
will work similar to the below example.
`(cl-symbol-macrolet ,loopy--generalized-vars (let* ,loopy--with-vars (let ,loopy--accumulation-vars (let* ,loopy--iteration-vars (let ((loopy--early-return-capture (cl-block ,loopy--loop-name ,@loopy--before-do (catch loopy--non-returning-exit-tag-name (while ,(cl-case (length loopy--pre-conditions) (0 t) (1 (car loopy--pre-conditions)) (t (cons 'and loopy--pre-conditions))) (catch loopy--skip-tag-name ,@loopy--main-body) ,@loopy--latter-body (unless ,loopy--post-conditions (cl-return-from ,loopy--loop-name ,loopy--implicit-return))) ,loopy--vars-final-updates ,@loopy--after-do)) ,loopy--implicit-return)) ,@loopy--final-do ,(if loopy--final-return loopy--final-return 'loopy--early-return-capture))))))
Next: An always
Command, Previous: Background Info, Up: Custom Commands [Contents][Index]
This is an example of a command that prints a message during the main loop body.
To implement a custom loop-body command, Loopy needs two pieces of information:
Importantly, your custom commands cannot share a name.
For example, say that you’re tired of typing out ‘(do (message "Hello, %s %s" PERSONAL-NAME FAMILY-NAME))’ and would prefer to instead use ‘(greet PERSONAL-NAME [FAMILY-NAME])’. This only requires adding code to the loop’s main body, so the definition of the parsing function is quite simple.
(require 'cl-lib) (cl-defun my-loopy-greet-command-parser ((_ personal-name &optional family-name)) "Greet one with PERSONAL-NAME and optional FAMILY-NAME." ;; Note: Use a generated variable to avoid evaluating `family-name' twice ;; (once in the `if' condition and once in the `message'). ;; ;; In newer Emacs, see also `cl-with-gensyms' and `cl-once-only', ;; also available from the Compat library ;; (https://elpa.gnu.org/packages/compat.html), on which Loopy depends. (let ((family-name-var (gensym "family-name"))) `((loopy--main-body (if-let ((,family-name-var ,family-name)) (message "Hello, %s %s" ,personal-name ,family-name-var) (message "Hello, %s" ,personal-name))))))
Loopy will pass the entire command expression to the parsing function, and expects that a list of instructions will be returned.
To tell Loopy about this function, add it and the command name ‘greet’ to the
variable loopy-command-parsers
, which associates commands with parsing
functions. The function that is paired with the symbol receives the entire
command expression, and should produce a list of valid instructions.
;; Using the Map library, for convenience: (require 'map) (setf (map-elt loopy-command-parsers 'greet) #'my-loopy-greet-command-parser)
After that, you can use your custom command in the loop body.
(loopy (list name '(("John" "Deer") ("Jane" "Doe") ("Jimmy"))) (greet (car name) (cadr name)))
By running M-x pp-macroexpand-last-sexp RET on the above expression, you can see that it expands to do what we want, as expected.
;; An example expansion: (let* ((list-166 '(("John" "Deer") ("Jane" "Doe") ("Jimmy"))) (name nil)) (cl-block nil (while (consp list-166) (setq name (car list-166)) (if-let ((family-name900 (cadr name))) (message "Hello, %s %s" (car name) family-name900) (message "Hello, %s" (car name))) (setq list-166 (cdr list-166))) nil))
Next: Custom commands in the loopy-iter
macro, Previous: Hello World, Up: Custom Commands [Contents][Index]
always
CommandLets say we want to emulate cl-loop
’s ‘always’ clause, which causes the loop
to return nil
if an expression evaluates to nil
and t
otherwise. This is
similar to the functions cl-every
and seq-every-p
.
Here is an example:
;; => t (cl-loop for i from 1 to 9 always (< i 10))
While loopy
already has an ‘always’ command, we’ll ignore it for the sake of
this example. Without a custom command, you could translate this using the
following code:
;; => t (loopy (numbers i :from 1 :to 9) (unless (< i 10) (return nil)) (else-do (cl-return t)))
This is similar to what you might write in other languages, such as Python.
# In some testing Python function: for i in range(1, 10): if not (i < 10): return False else: return True
While the meaning of the code is clear, this approach is certainly wordier.
Here’s how one could do this using a custom command. Again, Loopy already
comes with a built-in ‘always’ command. This example is taken directly from the
file loopy-commands.el, which contains the code of all of loopy
’s
built-in parsers.
We can describe the command’s desired behavior in two sentences:
nil
if the expression ever evaluates to
nil
.
t
if the loop is able to complete successfully.
This simplest way to satisfy the first requirement is to conditionally use
cl-return
if the expressions ever evaluates to nil
. We want to do this
while the loop is running, so we should use an instruction for
loopy--main-body
.
;; We want to insert something like the below code into the loop (unless CONDITION (cl-return nil)) ;; so we could use the instruction `(loopy--main-body (unless ,CONDITION (cl-return nil))) ;; in which CONDITION is supplied by the parsing function.
For a simple loop, this works well enough. However, this approach has two problems:
cl-return
would return
from the wrong cl-block
.
cl-return
directly causes the loop to exit immediately without
finalizing optimized accumulation values, so we wouldn’t be able to correctly
return multiple values from the macro.
The first problem could be solved by using the variable loopy--loop-name
to
retrieve the symbol naming the current loop, but that still leaves the second
problem. The better solution, when the condition is no longer meant, is to
store a nil
value in an accumulation command and to then leave the loop while
still finalizing variables, such as via the ‘leave’ command. This gives us
something like:
`(loopy--main-body (unless ,CONDITION (setq ,ACCUM-VAR nil) ,@(LEAVE-COMMAND-CODE)))
The best way to satisfy the second requirement is to set the initial value of
the accumulation variable to t
and to make that accumulation variable the
implied return value of the loop. As described in [BROKEN LINK: early-exit], leaving a loopy
early (such as via the ‘leave’ command) does not change the implied return value
of a loop. The logic of the command would be:
t
.
nil
, immediately set the accumulation
variable to nil
and leave the loop. If the condition is always non-nil
,
then we need do nothing.
With that in mind, our instructions for the loop would be
`((loopy--accumulation-vars (,ACCUM-VAR t)) (loopy--implicit-return ,ACCUM-VAR) (loopy--main-body (unless ,COND (setq ,ACCUM-VAR nil) ,@(LEAVE-COMMAND-CODE))))
Once we’ve chosen our instructions, we need to tell Loopy what function to use
to produce these instructions. Like in the previous example, we define the
parsing function and add it to loopy-command-parsers
.
;; As noted in the previous section, the parsing function is always ;; passed the entire command as `(always CONDTION)', not just the ;; command arguments as `CONDITION'. (require 'cl-lib) (require 'map) (require 'seq) (require 'loopy) (cl-defun my--loopy-always-command-parser ((_ condition)) "Parse a command of the form `(always CONDITION)'. If any condition is nil, `loopy' should immediately return nil. Otherwise, `loopy' should return t." (let ((result-var 'loopy-result)) `((loopy--accumulation-vars (,result-var t)) (loopy--implicit-return ,result-var) ;; Get the main-body expressions of `leave' and splice ;; into the `unless' body. The other `leave' instructions ;; are unmodified and included in the output of the parser. ,@(loopy (accum-opt main-exprs other-instrs) (list (&whole instr place expr) (loopy--parse-leave-command '(leave))) (if (eq place 'loopy--main-body) (collect main-exprs expr) (collect other-instrs instr)) (finally-return `(,@other-instrs (loopy--main-body (unless ,condition (setq ,result-var nil) ,@main-exprs)))))))) (setf (map-elt loopy-command-parsers 'always) #'my--loopy-always-command-parser)
Once we’ve added our parsing function to loopy-command-parsers
, Loopy will use
that function whenever it tries to understand the ‘always’ command. In this
case, this custom parser would supercede the built-in parser. An example output
of parsing an example command would be:
(my--loopy-always-command-parser '(always (< i 10))) ;; => ((loopy--accumulation-vars (loopy-result t)) (loopy--implicit-return loopy-result) (loopy--non-returning-exit-used loopy--non-returning-exit-tag) (loopy--main-body (unless (< i 10) (setq loopy-result nil) (throw 'loopy--non-returning-exit-tag t))))
Here are some examples of the command in action:
;; One condition: => t (loopy (list i (number-sequence 1 9)) (always (< i 10))) ;; Two conditions: => nil (loopy (list i (number-sequence 1 9)) (list j '(2 4 6 8 9)) (always (< i 10) (cl-evenp j))) ;; The previous example is equivalent to this. (loopy (list i (number-sequence 1 9)) (list j '(2 4 6 8 9)) (always (and (< i 10) (cl-evenp j))))
The actual definition of the ‘always’ command can be read in the library loopy-commands.el. It is close to the above definition, but includes more error checking for working with the other commands.
Next: Finding More Examples, Previous: An always
Command, Up: Custom Commands [Contents][Index]
loopy-iter
macroSee also The loopy-iter
Macro.
loopy-iter
works by:
loopy
sub-commands
Most loop commands should work with loopy-iter
without any changes. Some loop
commands are just loopy
versions of built-in Lisp macros or special forms, and
so aren’t needed in loopy-iter
in the first place.
However, special consideration is needed for commands that produce main-body code outside of main-body instructions, or that assume all of their arguments are loop commands. Consider an example definition of the parser for the ‘at’ command:
(cl-defun loopy--parse-at-command ((_ target-loop &rest commands)) "Parse the `at' command as (at &rest COMMANDS). These commands affect other loops higher up in the call list." (loopy--check-target-loop-name target-loop) (let ((loopy--loop-name target-loop) (loopy--in-sub-level t)) `((loopy--at-instructions (,target-loop ,@(loopy--parse-loop-commands commands))))))
All it does is process its arguments and wrap the resulting instructions in an
‘at’ instruction. By wrapping main-body instructions in an ‘at’ instruction,
loopy-iter
does not see the main-body code, and so cannot use it. Instead, an
alternative parser must be used:
(cl-defun loopy-iter--parse-at-command ((_ target-loop &rest commands)) "Parse the `at' command as (at &rest COMMANDS). These commands affect other loops higher up in the call list." (loopy--check-target-loop-name target-loop) ;; We need to capture all non-main-body instructions into a new `at' ;; instruction, so we just temporarily `let'-bind ;; `loopy-iter--non-main-body-instructions' while the expanding functions push ;; to it, which we then wrap back in a new instruction and pass up to the ;; calling function, which consumes instructions. (loopy (with (loopy-iter--non-main-body-instructions nil) (loopy--loop-name target-loop) (loopy--in-sub-level t) (loopy-iter--level (1+ loopy-iter--level))) (list cmd commands) (collect (list 'loopy--main-body (macroexpand-all cmd macroexpand-all-environment))) (finally-return ;; Return list of instructions to comply with expectations of calling ;; function, which thinks that this is a normal loop-command parser. `(,@loopy-result (loopy--at-instructions (,target-loop ,@(thread-last loopy-iter--non-main-body-instructions nreverse (apply #'append))))))))
This loopy-iter
-specific definition separates the main-body instructions out
from the other instructions so that loopy-iter
can operate on their values
directly. The variable loopy-iter--non-main-body-instructions
is a place
where the macro-expanding parsers used by loopy-iter
can push lists of
instructions (see the code for usage examples).
These alternative command parsers are listed in
loopy-iter-overwritten-command-parsers
.
Previous: Custom commands in the loopy-iter
macro, Up: Custom Commands [Contents][Index]
If you would like to see more examples, consider reading through the source code of loopy-commands.el, which contains the code of all of the built-in loop commands. You can easily find this file using M-x find-library loopy-commands RET.
Next: Translating to and from ‘cl-loop’, Previous: Custom Commands, Up: Loopy: A Looping and Iteration Macro [Contents][Index]
cl-loop
loopy
is a better version of cl-loop
, judging by the following:
cl-loop
, though some optimizations are more
explicit.
loopy
provides a more complete destructuring system, which can be swapped out
with others.
;; No array destructuring in `cl-loop'. ;; => (3 7) (loopy (list [i j] '([1 2] [3 4])) (collect (+ i j)))
loopy
provides more looping constructs out of the box with a (according to
the author) clearer naming scheme than cl-loop
’s ‘for’ clauses.
(loopy (list i my-var1) (array j my-var2) (collect (cons i j))) (cl-loop for i in my-var1 for j across my-var2 collect (cons i j))
loopy
commands are more
featureful than their cl-loop
counterparts.
;; `array' is more featureful than `for-across'. ;; => ((9 . 20) (7 . 18) (5 . 16) (3 . 14) (1 . 12)) (loopy (array i [11 12 13 14 15 16 17 18 19 20] :downfrom 9 :by 2 :to 1 :index idx) (collect (cons idx i)))
loopy
provides more control over what happens after and around the loop.
cl-loop
lacks equivalents of the following special macro arguments:
finally do
.
cl-loop
from binding variables.
loopy
can wrap only the loop body in
unwind-protect
.
cl-loop
, even when they don’t need to be.
cl-loop
.
loopy
is more extensible. Loop commands can be added easily.
loopy-iter
, the macro is more flexible in how it can be used.
Next: Macro Argument and Loop Command Index, Previous: Comparing to cl-loop
, Up: Loopy: A Looping and Iteration Macro [Contents][Index]
loopy
and cl-loop
use slightly different terminology. The equivalent of
“for clauses” are referred to as “iteration commands” in loopy
, as they are
generally used for iterating through sequences. Meanwhile, “iteration clauses”
can be separated into “iteration commands” (‘list’ and ‘array’) and “early-exit
commands” (‘while’ and ‘until’).
“Accumulation clauses” work the same as “accumulation commands”.
Next: Iteration Clauses, Up: Translating to and from ‘cl-loop’ [Contents][Index]
As Emacs has many functions that return lists, there is no need to implement an
exact equivalent for every ‘for’-clause that cl-loop
has. Instead, one can
just iterate through the return value of the appropriate function using the
‘list’ command.
For the commands operating on hash tables, see also the generic iteration command ‘map-pairs’, which works generically on hash tables, association lists (“alists”), property lists (“plists”), and vectors.
cl-loop | loopy |
---|---|
‘for VAR from EXPR1 to EXPR2 by EXPR3’ | ‘(numbers VAR :from EXPR1 :to EXPR2 :by EXPR3)’ |
‘for VAR in LIST [by FUNCTION]’ | ‘(list VAR LIST :by FUNC)’ |
‘for VAR on LIST [by FUNCTION]’ | ‘(cons VAR VAL :by FUNC)’ |
‘for VAR in-ref LIST by FUNCTION’ | ‘(list-ref VAR LIST :by FUNC)’ |
‘for VAR across ARRAY’ | ‘(array VAR ARRAY)’ |
‘for VAR across-ref ARRAY’ | ‘(array-ref VAR ARRAY)’ |
‘for VAR being the elements of SEQUENCE’ | ‘(sequence VAR SEQUENCE)’ |
‘for VAR being the elements of-ref SEQUENCE’ | ‘(sequence-ref VAR SEQUENCE)’ |
‘for VAR being the symbols [of OBARRAY]’ | None so far. Use mapatoms . |
‘for VAR being the hash-keys of HASH-TABLE’ | ‘(list VAR (hash-table-keys HASH-TABLE))’ |
‘for VAR being the hash-values of HASH-TABLE’ | ‘(list VAR (hash-table-values HASH-TABLE))’ |
‘for VAR being the key-codes of KEYMAP’ | None so far. Use map-keymap . |
‘for VAR being the key-bindings of KEYMAP’ | None so far. Use map-keymap . |
‘for VAR being the key-seqs of KEYMAP’ | None so far. |
‘for VAR being the overlays [of BUFFER]’ | None so far. Use overlay-lists . |
‘for VAR being the intervals [of BUFFER]’ | None so far. |
‘for VAR being the frames’ | ‘(list VAR (frame-list))’ |
‘for VAR being the windows [of FRAME]’ | ‘(list VAR (window-list FRAME))’ |
‘for VAR being the buffers’ | ‘(list VAR (buffer-list))’ |
‘for VAR = EXPR1 then EXPR2’ | ‘(set VAR EXPR1 EXPR2)’ |
Next: Accumulation Clauses, Previous: For Clauses, Up: Translating to and from ‘cl-loop’ [Contents][Index]
cl-loop | loopy |
---|---|
‘repeat INT’ | ‘(cycle INT)’ |
‘while COND’ | ‘(while COND)’ |
‘until COND’ | ‘(until COND)’ |
‘iter-by iterator’ | ‘(iter ITERATOR)’ |
‘never’ | ‘(never COND)’ |
‘always’ | ‘(always COND)’ |
‘thereis’ | ‘(thereis COND)’ |
Next: Other Clauses, Previous: Iteration Clauses, Up: Translating to and from ‘cl-loop’ [Contents][Index]
Like with cl-loop
, in loopy
, accumulation commands accumulate into the same
variable when no ‘VAR’ is given (by default, loopy-result
).
cl-loop | loopy |
---|---|
‘append EXPR into VAR’ | ‘(append VAR EXPR)’ |
‘collect EXPR into VAR’ | ‘(collect VAR EXPR)’ |
‘concat EXPR into VAR’ | ‘(concat VAR EXPR)’ |
‘count EXPR into VAR’ | ‘(count VAR EXPR)’ |
‘maximize EXPR into VAR’ | ‘(maximize VAR EXPR)’ |
‘minimize EXPR into VAR’ | ‘(minimize VAR EXPR)’ |
‘nconc EXPR into VAR’ | ‘(nconc VAR EXPR)’ |
‘sum EXPR into VAR’ | ‘(sum VAR EXPR)’ |
‘vconcat EXPR into VAR’ | ‘(vconcat VAR EXPR)’ |
Previous: Accumulation Clauses, Up: Translating to and from ‘cl-loop’ [Contents][Index]
In loopy
, ‘if’, ‘when’, and ‘unless’ can take multiple loop commands as
arguments, and operate more like their Lisp counterparts.
This means that ‘if’ is not a synonym for ‘when’. Just like the normal Lisp
special form if
, ‘(if COND cmd1 cmd2 cmd3)’ only runs ‘cmd1’ if ‘COND’
evaluates to non-nil, and only runs commands ‘cmd2’ and ‘cmd3’ if ‘COND’
evaluates to nil
.
loopy
also provides the command ‘cond’, which works like the normal Lisp
special form cond
.
cl-loop | loopy |
---|---|
‘with var = value’ | ‘(with (VAR VALUE))’ as a macro argument |
‘if COND clause’ | ‘(if COND CMDS)’ as a loop command |
‘when COND clause’ | ‘(when COND CMDS)’ as a loop command |
‘unless COND clause’ | ‘(unless COND CMDS)’ as a loop command |
‘named NAME’ | ‘NAME’ or ‘(named NAME)’ as a macro argument |
‘initially [do] EXPRS’ | ‘(before-do EXPRS)’ as a macro argument |
‘finally [do] EXPRS’ | ‘(finally-do EXPRS)’ as a macro argument |
‘finally return EXPR’ | ‘(finally-return EXPR)’ as a macro argument |
‘do EXPRS’ | ‘(do EXPRS)’ as a loop command |
‘return EXPR’ | ‘(return EXPR)’ as a loop command |
Next: Variable Index, Previous: Translating to and from ‘cl-loop’, Up: Loopy: A Looping and Iteration Macro [Contents][Index]
Jump to: | A B C D E F I L M N O P R S T U V W |
---|
Jump to: | A B C D E F I L M N O P R S T U V W |
---|
Next: Concept Index, Previous: Macro Argument and Loop Command Index, Up: Loopy: A Looping and Iteration Macro [Contents][Index]
Jump to: | L |
---|
Jump to: | L |
---|
Previous: Variable Index, Up: Loopy: A Looping and Iteration Macro [Contents][Index]
Jump to: | &
_
A C D F I K L O P S T V |
---|
Jump to: | &
_
A C D F I K L O P S T V |
---|
Strings being a kind of array. See (elisp)Sequences Arrays Vectors for more.
https://github.com/magnars/dash.el