Loopy: A Looping and Iteration Macro

Next:   [Contents][Index]

Loopy: A Looping and Iteration Macro

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)).

Table of Contents


1 Introduction

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:

All that being said, Loopy is not yet feature complete. Please request features or report problems in this project’s issues tracker.


2 Basic Concepts

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.


3 Special Macro Arguments

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.

named’ or just a symbol

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))))
with’, ‘let*’, ‘init

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'.
without’, ‘no-with’, ‘no-init

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))))
before-do’, ‘before’, ‘initially-do’, ‘initially

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))
after-do’, ‘after’, ‘else-do’, ‘else

Run Lisp expressions after the loop successfully completes. This is similar to Python’s else statement following a for or while loop. Unlike progn, the return values of the expressions do not affect the return value of the macro.

;; 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.")))
finally-do’, ‘finally

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))
finally-return

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))
finally-protect’, ‘finally-protected

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)))
flag’, ‘flags

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))
accum-opt’, ‘opt-accum

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))
wrap

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 that loopy creates is wrapped by a cl-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:

  1. Using cl-return in ‘before-do’ will prevent the both loop and ‘after-do’ code from running.
  2. 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’s else statement when used with loops.

These three sections (‘before-do’, ‘after-do’, and the while-loop itself) are the only structures that occur within the cl-block. Using cl-return in ‘before-do’, for example, will not stop code in ‘finally-do’ from running or values listed in ‘finally-return’ from being returned.


4 Loop Commands

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
setsetting
listlisting
collectcollecting
numbersnumbering

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:

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)

4.1 Basic Destructuring

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:

  • Destructuring arrays
  • Destructuring in accumulation commands (Accumulation)
  • Destructuring in commands iterating through setf-able places in a sequence (Sequence Reference Iteration)
  • The extended forms of the ‘&optional’ and ‘&key’ variables (such as default values like in ... &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.
  • A ‘&map’ construct, similar to ‘&key’, but using 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 ‘dash2 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).

  • To destructure lists, use a list, as in ‘(a b c)’.
  • To destructure arrays, use a vector, as in ‘[a b c]’.
  • To destructure sequences generically using ‘seq.el’ (mainly via 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:

  • A positional variable which will be bound to the corresponding element in the sequence. These variables can themselves be sequences, but must be of the correct type.
    ;; ((1 2 3) (4 5 6))
    (loopy (list [i (j k)] '([1 (2 3)] [4 (5 6)]))
           (collect (list i j k)))
    
  • The symbol ‘_’ (an underscore) or a symbol beginning with an underscore: This means to ignore the element at this location. This can be more efficient.
    ;; 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))
    
  • The symbol ‘&whole’: If ‘&whole’ is the first element in the sequence (or the second element if ‘&seq’ is the first), then the following element of the sequence names a variable that holds the entire value of what is destructured.

    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)))
    
  • The symbol ‘&rest’: A variable named after ‘&rest’ contains the remaining elements of the destructured value after any positional and optional values. When destructuring lists, one can also use dotted notation, as in a CL 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)))
    
  • The symbol ‘&optional’: A variable named after ‘&optional’ is bound if the sequence is long enough to have a value at that position. If the sequence is not long enough, then the variable is bound to 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:

    • (VAR DEFAULT SUPPLIED)’ or ‘[VAR DEFAULT SUPPLIED]’, in which ‘VAR’ itself can be a sequence
    • (VAR DEFAULT)’ or ‘[VAR DEFAULT]’, in which ‘VAR’ itself can be a sequence
    • (VAR)’ or ‘[VAR]’, in which ‘VAR’ itself can be a sequence
    • a symbol ‘VAR
    ;; => (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)))
    
  • The symbol ‘&key’ or ‘&keys’: Variables named after ‘&key’ are transformed into keys whose values will be sought using 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:

    • ((VAR KEY) DEFAULT SUPPLIED)’, ‘[[VAR KEY] DEFAULT SUPPLIED]’, ‘([VAR KEY] DEFAULT SUPPLIED)’, or ‘[(VAR KEY) DEFAULT SUPPLIED]’, in which ‘VAR’ itself can be a sequence
    • ((VAR KEY) DEFAULT)’, ‘[[VAR KEY] DEFAULT]’, ‘([VAR KEY] DEFAULT)’, or ‘[(VAR KEY) DEFAULT]’, in which ‘VAR’ itself can be a sequence
    • ((VAR KEY))’, ‘[[VAR KEY]]’, ‘([VAR KEY])’, or ‘[(VAR KEY)]’, in which ‘VAR’ itself can be a sequence
    • (VAR DEFAULT SUPPLIED)’ or ‘[VAR DEFAULT SUPPLIED]’, in which ‘VAR’ is a symbol
    • (VAR DEFAULT)’ or ‘[VAR DEFAULT]’, in which ‘VAR’ is a symbol
    • (VAR)’ or ‘[VAR]’, in which ‘VAR’ is a symbol
    • a symbol ‘VAR

    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))
    
  • The symbol ‘&map’: Variables after ‘&map’ are bound similarly to map-let from the library ‘map.el’. ‘&map’ works similarly to ‘&key’, but has a few important differences:
    1. Maps are more generic than property lists (“plists”). A “map” is a generic structure which supports the function 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.
    2. &map’ will not signal an error if there are unused keys inside the destructured value; there is no ‘&allow-other-keys’ for ‘map’. In the same vein, it cannot be made to signal an error if there are unused keys.

    Variables after ‘&map’ can be of the following forms:

    • (KEY VAR DEFAULT SUPPLIED)’ or ‘[KEY VAR DEFAULT SUPPLIED]’, in which ‘VAR’ itself can be a sequence
    • (KEY VAR DEFAULT)’ or ‘[KEY VAR DEFAULT]’, in which ‘VAR’ itself can be a sequence
    • (KEY VAR)’ or ‘[KEY VAR]’, in which ‘VAR’ itself can be a sequence
    • (VAR)’ or ‘[VAR]’, in which ‘VAR’ is a symbol
    • a symbol ‘VAR

    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)))
    
  • The symbol ‘&aux’: Variables named after ‘&aux’ are bound to the given values. Like in CL Lib, ‘&aux’ must come last in the sequence.
    ;; => (7 7 7)
    (loopy (cycle 3)
           (collect (&aux [coll 7]) 'ignored)
           (finally-return coll))
    
  • The symbol ‘&seq’: If the first symbol in the sequence is ‘&seq’, then the sequence will be destructured as a generic sequence using the generic-sequence library ‘seq.el’. Specifically, destructuring is similar to using 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)))
    

4.2 Generic Evaluation

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.

(do EXPRS)

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)))
(command-do [CMDS])

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)))
(set VAR [EXPRS])

Bind ‘VAR’ to each ‘EXPR’ in order. Once the last ‘EXPR’ is reached, it is used repeatedly for the rest of the loop. With no ‘EXPR’, ‘VAR’ is bound to nil during each iteration of the loop.

This command also has the aliases ‘setting’.

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))
(set-prev VAR VAL &key back)

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))

4.3 Iteration

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.


4.3.1 Generic Iteration

(cycle|repeat [VAR] EXPR)

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))
(iter [VAR] EXPR &key close yield-result)

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))))

4.3.2 Numeric Iteration

For iterating through numbers, there is the general ‘numbers’ command, and its variants ‘numbers-up’ and ‘numbers-down’.

(numbers|nums VAR &key KEYS)

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.

(numbers-down|nums-down VAR START [END] &key by)

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))
(numbers-up|nums-up VAR START [END] &key by)

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))

4.3.3 Sequence Iteration

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))
(array|string VAR EXPR [EXPRS] &key KEYS)

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))
(cons|conses VAR EXPR &key by)

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))
(list|each VAR EXPR [EXPRS] &key by)

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))
(map|map-pairs VAR EXPR &key unique)

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:

  1. This is more consistent with the command ‘map-ref’, for which such duplicates are more likely to cause errors.
  2. For maps that can have duplicate keys (such as alists and plists), there are already other iteration commands (such as ‘list’ and ‘cons’) that explicitly include the duplicates.

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)))
(sequence VAR EXPR [EXPRS] &key KEYS)

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 and elt. 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))
(seq VAR EXPR [EXPRS] &key KEYS)

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))
(stream VAR EXPR &key by)

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)))
(substream VAR EXPR &key by length)

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 ‘streamfrom 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)))

4.3.4 Sequence Index Iteration

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.

(sequence-index VAR EXPR &key KEYS)

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:

  • array-index’, ‘arraying-index
  • list-index’, ‘listing-index
  • string-index’, ‘stringing-index
  • sequencing-index
  • seq-index’, ‘seqing-index

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)))

4.3.5 Sequence Reference Iteration

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.

(array-ref|string-ref VAR EXPR &key KEYS)

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))
(list-ref VAR EXPR &key by)

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))
(map-ref VAR EXPR &key key unique)

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))
(sequence-ref VAR EXPR &key KEYS)

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))
(seq-ref VAR EXPR &key KEYS)

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))

4.4 Accumulation

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.


4.4.1 Common Properties of Accumulation Commands

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.

at

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))
into

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))
test

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:

  1. Tests default to equal, like in other Emacs Lisp libraries, not eql.
  2. 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 and memq.
;; 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))
key

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.


4.4.2 Generic Accumulation

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.

  • reduce’ is like cl-reduce, calling a function that receives (1) the accumulation variable and (2) the value to accumulate, in that order.
  • accumulate’ works by calling a function that receives (1) the value to accumulate and (2) the accumulation variable, in that order.
  • set-accum’ is the most generic, and works like ‘set’ for only one value.

The commands are described in more detail below.

(reduce VAR EXPR FUNC)

Reduce ‘EXPR’ into ‘VAR’ by ‘FUNC’, like in cl-reduce and (funcall FUNC VAR EXPR). ‘FUNC’ is called with ‘VAR’ as the first argument and ‘EXPR’ as the second argument. This is unlike ‘accumulate’, which gives ‘VAR’ and ‘EXPR’ to ‘FUNC’ in the opposite order (that is, ‘EXPR’ first, then ‘VAR’).

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|accumulating VAR EXPR FUNC)

Accumulate the result of applying function ‘FUNC’ to ‘EXPR’ and ‘VAR’ like in (funcall FUNC EXPR VAR). ‘EXPR’ and ‘VAR’ are used as the first and second arguments to ‘FUNC’, respectively.

;; 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-accum VAR EXPR)

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))

4.4.3 Numeric Accumulation

Numeric accumulation work on numbers, such as by repeatedly adding or multiplying values together.

(count VAR EXPR)

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))
(maximize|max VAR EXPR)

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))
(minimize|min VAR EXPR)

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))
(multiply VAR EXPR)

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))
(sum VAR EXPR)

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))

4.4.4 Sequence Accumulation

Sequence accumulation commands are used to join lists (such as ‘union’ and ‘append’) and to collect items into lists (such as ‘collect’ and ‘adjoin’).

(adjoin VAR EXPR &key at test key)

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))
(append VAR EXPR &key at)

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 VAR EXPR &key at)

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))
(concat VAR EXPR &key at)

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))
(nconc VAR EXPR &key at)

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))
(nunion VAR EXPR &key test at key)

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))
(prepend VAR EXPR)

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)))
(push-into|push VAR EXPR)

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))
(union VAR EXPR &key test at key)

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))
(vconcat VAR EXPR &key at)

Repeatedly concatenate the value of ‘EXPR’ onto ‘VAR’ via the function vconcat. For concatenating values into a string, see the command ‘concat’.

This command also has the alias ‘vconcating’.

;; => [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))

4.4.5 Other Accumulation Commands

(find VAR EXPR TEST &key ON-FAILURE)

If the expression ‘TEST’ is non-nil, then the loop stops and ‘VAR’ is set to the value of ‘EXPR’. If ‘TEST’ is never non-nil, then ‘VAR’ is set to the value of ‘ON-FAILURE’, if provided.

This command also has the alias ‘finding’.

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))

4.4.6 Optimizing Accumulations

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

  1. Accumulation into the named variable coll has been explicitly optimized for using the end of the sequence, despite there being more commands that use the beginning of the sequence.
  2. Because accumulation into 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: , Previous: , Up: Loop Commands   [Contents][Index]

4.5 Checking Conditions

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.

(always [VAR] EXPR &key into)

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:

  1. The variable (by default, loopy-result) is initially bound to t, using it as the implicit return value of the loop.
  2. When the condition is checked, the variable is bound to the value of the condition.
  3. If the variable is nil, the loop is exited.
  4. If the loop completes successfully, then the variable is the final value of the condition. If the command is never run, then the variable will remain 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"))))
(never [VAR] EXPR &key into)

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:

  1. The variable (by default, loopy-result) is initialized to t and used as the loop’s implicit return value.
  2. The value of the condition is checked.
  3. If the condition is non-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))
(thereis [VAR] EXPR &key into)

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:

  1. The variable (by default, loopy-result) is initialized to nil and used as the implicit return value of the loop.
  2. The value of the condition is stored in the variable.
  3. If the value of the variable is non-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)))

4.6 Control Flow


4.6.1 Conditionals

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.

(cond [(EXPR CMDS) [...]])

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))
(if EXPR CMDS)

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))
(when EXPR CMDS)

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))
(unless EXPR CMDS)

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: , Previous: , Up: Control Flow   [Contents][Index]

4.6.2 Skipping Cycles

(skip|continue)

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-from|continue-from NAME)

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))

4.6.3 Early Exit

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)

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-from|leaving-from NAME)

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 [EXPRS])

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 NAME [EXPRS])

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?))))
(while COND)

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))
(until COND)

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: , Up: Loop Commands   [Contents][Index]

4.7 Sub-Loops

Loopy provides two sets of commands for working with sub-loops:

  1. The ‘loopy’ and ‘loopy-iter’ commands, which correctly expand inner loops during the expansion of outer loops.
  2. The ‘at’ command, which controls the named outer loop that commands interact with. For example, it can control to which loop an implied accumulation variable is scoped and so for which loop that variable is used as an implied return value.

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 and loopy-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.

(loopy [SPECIAL-MACRO-ARGUMENTS or CMDS])

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)))))
(loopy-iter [SPECIAL-MACRO-ARGUMENTS or CMDS or LISP-EXPRS])

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))))))
(at LOOP-NAME [CMDS])

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))

5 Destructuring Macros

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))

6 The loopy-iter Macro

loopy-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,

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:

  1. You should not rely on the value of a loop command’s expanded code. Such expanded code is an implementation detail and subject to change.
  2. Some macros, especially those that interact with each other, produce broken code while 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.


6.1 Default Bare Names in 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:

  • do’ and ‘command-do’, which are not needed.
  • Commands that exists only as a command version of an existing Lisp feature, such as ‘if’, ‘cond’, ‘when’, and ‘unless’.
  • The commands ‘loopy’ and ‘loopy-iter’. The macro 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’, as it would conflict with the special form while. Use (when (not COND) (leaving)) or (unless COND (leaving)) instead.
  • until’, to be consistent with the exclusion of ‘while’.

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)))))
  • Special Macro Argument Names:
    • accum-opt
    • after-do
    • after
    • before-do
    • before
    • else-do
    • else
    • finally-do
    • finally-protect
    • finally-protected
    • finally-return
    • finally
    • flag
    • flags
    • init
    • initially-do
    • initially
    • no-init
    • no-with
    • opt-accum
    • with
    • without
    • wrap
  • Command Names:
    • Iteration Command Names:
      • Generic Iteration Command Names:
        • cycling
        • repeating
      • Numeric Iteration Command Names:
        • numbering
        • numbering-down
        • numbering-up
      • Sequence Iteration Command Names:
        • arraying
        • consing
        • listing
        • mapping-pairs
        • mapping
        • seqing
        • sequencing
        • stringing
      • Sequence Index Iteration Command Names:
        • arraying-index
        • listing-index
        • sequencing-index
        • seqing-index
        • stringing-index
      • Sequence Reference Iteration Command Names:
        • arraying-ref
        • listing-ref
        • mapping-ref
        • sequencing-ref
        • seqing-ref
        • stringing-ref
    • Accumulation Commands:
      • accumulating
      • adjoining
      • appending
      • collecting
      • concating
      • counting
      • finding
      • maximizing
      • minimizing
      • multiplying
      • nconcing
      • nunioning
      • prepending
      • pushing
      • pushing-into
      • reducing
      • summing
      • unioning
      • vconcating
    • Boolean Commands:
      • always
      • never
      • thereis
    • Cycle-Skipping Commands:
      • continuing
      • continuing-from
      • skipping
      • skipping-from
    • Early-Exit Commands:
      • leaving
      • leaving-from
      • returning
      • returning-from
    • Sub-loop Commands:
      • at

7 Using Flags

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:

pcase

Use pcase-let for destructuring ((elisp)Destructuring with pcase Patterns).

seq

Use seq-let for destructuring ((elisp)seq-let).

dash

Use the style of destructuring found in the ‘dash’ library ((dash)-let).

default

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?))

8 Custom Aliases

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 ArgumentBuilt-In Aliases
arraystring
seq-refsequence-ref’, ‘seqf
after-doafter’, ‘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.


9 Custom Commands

This section contains information about how loop commands work and how one can add custom commands to loopy. Two examples are provided.


9.1 Background Info

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:

  • Declaring a given variable in a let-like form to make sure it’s locally scoped.
  • Declaring a generated variable in a let-like form to contain a given value.
  • Adding a condition for continuing/exiting the loop.
  • Adding code to be run during the main processing section of the while-loop. This location is referred to as the main body of the loop.
  • Adding code to be run after the main processing section, such as for updating variables. This location is referred to as the latter 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.

PlaceCode
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:

loopy--generalized-vars

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.

loopy--iteration-vars

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

loopy--accumulation-vars

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.

loopy--other-vars

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.

loopy--pre-conditions

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.

loopy--main-body

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:

  • code that updating some iteration variables, such as the ‘i’ in ‘(list i my-list)
  • code that updating accumulation variables, such as the ‘coll’ in ‘(collect coll my-value)
  • code that exits the loop early, such as from the ‘leave’ command
loopy--latter-body

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.

loopy--post-conditions

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:

loopy--implicit-return

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’.

loopy--vars-final-updates

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:

  1. Correcting the order of elements in a list that was constructed in reverse.
  2. Coercing the variable into a new sequence type.
  3. Appending a list of lists.

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))))
loopy--at-instructions

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.

loopy--before-do

Expressions to evaluate before the loop. These are derived from the ‘before-do’ macro argument.

loopy--after-do

Expressions to evaluate after the loop completes successfully. These are derived from the ‘after-do’ macro argument.

loopy--final-do

Expressions to evaluate after the loop completes, regardless of success. These are derived from the ‘finally-do’ macro argument.

loopy--final-return

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))))))

9.2 Hello World

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:

  1. The keyword that names your command
  2. The parsing function that can turn uses of your command into instructions.

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))

9.3 An always Command

Lets 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:

  1. The loop should immediately return nil if the expression ever evaluates to nil.
  2. The loop should return 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:

  1. If the loop is named something other than ‘nil’, cl-return would return from the wrong cl-block.
  2. Using 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:

  1. Initialize the accumulation variable to t.
  2. During the loop, if the condition is nil, immediately set the accumulation variable to nil and leave the loop. If the condition is always non-nil, then we need do nothing.
  3. Regardless of what happens during the loop, the implied return value is used as the return value of the macro, barring any use of the ‘finally-return’ macro argument.

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.


9.4 Custom commands in the loopy-iter macro

See also The loopy-iter Macro.

loopy-iter works by:

  1. separating main-body instructions from other instructions
  2. expanding that main-body code for loopy sub-commands
  3. collected those expansions into the outputted loop’s body
  4. processing the collected non-main-body instructions

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.


9.5 Finding More Examples

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.


10 Comparing to cl-loop

loopy is a better version of cl-loop, judging by the following:


11 Translating to and from ‘cl-loop

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”.


11.1 For Clauses

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-looploopy
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 KEYMAPNone so far. Use map-keymap.
for VAR being the key-bindings of KEYMAPNone so far. Use map-keymap.
for VAR being the key-seqs of KEYMAPNone 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)

11.2 Iteration Clauses

cl-looploopy
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)

11.3 Accumulation Clauses

Like with cl-loop, in loopy, accumulation commands accumulate into the same variable when no ‘VAR’ is given (by default, loopy-result).

cl-looploopy
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)

11.4 Other Clauses

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-looploopy
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 NAMENAME’ 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

12 Macro Argument and Loop Command Index

Jump to:   A   B   C   D   E   F   I   L   M   N   O   P   R   S   T   U   V   W  
Index Entry  Section

A
accum-opt: Special Macro Arguments
accumulate: Generic Accumulation
accumulating: Generic Accumulation
adjoin: Sequence Accumulation
adjoining: Sequence Accumulation
after: Special Macro Arguments
after-do: Special Macro Arguments
always: Checking Conditions
append: Sequence Accumulation
appending: Sequence Accumulation
array: Sequence Iteration
array-index: Sequence Index Iteration
array-ref: Sequence Reference Iteration
arraying: Sequence Iteration
arraying-index: Sequence Index Iteration
arraying-ref: Sequence Reference Iteration
at: Sub-Loops

B
before: Special Macro Arguments
before-do: Special Macro Arguments

C
collect: Sequence Accumulation
collecting: Sequence Accumulation
command-do: Generic Evaluation
concat: Sequence Accumulation
concating: Sequence Accumulation
cond: Conditionals
cons: Sequence Iteration
conses: Sequence Iteration
consing: Sequence Iteration
continue: Skipping Cycles
continue-from: Skipping Cycles
continuing: Skipping Cycles
continuing-from: Skipping Cycles
count: Numeric Accumulation
counting: Numeric Accumulation
cycle: Generic Iteration
cycling: Generic Iteration

D
do: Generic Evaluation

E
each: Sequence Iteration
else: Special Macro Arguments
else-do: Special Macro Arguments
expr: Generic Evaluation
exprs: Generic Evaluation

F
finally: Special Macro Arguments
finally-do: Special Macro Arguments
finally-protect: Special Macro Arguments
finally-protected: Special Macro Arguments
finally-return: Special Macro Arguments
find: Other Accumulation Commands
finding: Other Accumulation Commands
flag: Special Macro Arguments
flags: Special Macro Arguments

I
if: Conditionals
init: Special Macro Arguments
initially: Special Macro Arguments
initially-do: Special Macro Arguments
iter: Generic Iteration
iterating: Generic Iteration

L
leave: Early Exit
leave-from: Early Exit
leaving: Early Exit
leaving-from: Early Exit
let*: Special Macro Arguments
list: Sequence Iteration
list-index: Sequence Index Iteration
list-ref: Sequence Reference Iteration
listing: Sequence Iteration
listing-index: Sequence Index Iteration
listing-ref: Sequence Reference Iteration
loopy command: Sub-Loops
loopy--parse-loop-command: Background Info
loopy--parse-loop-commands: Background Info
loopy-defalias: Custom Aliases
loopy-iter: The loopy-iter Macro
loopy-iter command: Sub-Loops
loopy-lambda: Destructuring Macros
loopy-let*: Destructuring Macros
loopy-ref: Destructuring Macros
loopy-setq: Destructuring Macros

M
map: Sequence Iteration
map-pairs: Sequence Iteration
map-ref: Sequence Reference Iteration
mapping: Sequence Iteration
mapping-pairs: Sequence Iteration
mapping-ref: Sequence Reference Iteration
max: Numeric Accumulation
maximize: Numeric Accumulation
maximizing: Numeric Accumulation
maxing: Numeric Accumulation
min: Numeric Accumulation
minimize: Numeric Accumulation
minimizing: Numeric Accumulation
minnning: Numeric Accumulation
multiply: Numeric Accumulation
multiplying: Numeric Accumulation

N
named: Special Macro Arguments
nconc: Sequence Accumulation
nconcing: Sequence Accumulation
never: Checking Conditions
no-init: Special Macro Arguments
no-with: Special Macro Arguments
num: Numeric Iteration
number: Numeric Iteration
numbering: Numeric Iteration
numbering-down: Numeric Iteration
numbering-up: Numeric Iteration
numbers: Numeric Iteration
numbers-down: Numeric Iteration
numbers-up: Numeric Iteration
nums: Numeric Iteration
nunion: Sequence Accumulation
nunioning: Sequence Accumulation

O
opt-accum: Special Macro Arguments

P
prepend: Sequence Accumulation
prepending: Sequence Accumulation
prev-set: Generic Evaluation
push: Sequence Accumulation
push-into: Sequence Accumulation
pushing: Sequence Accumulation
pushing-into: Sequence Accumulation

R
reduce: Generic Accumulation
reducing: Generic Accumulation
repeat: Generic Iteration
repeating: Generic Iteration
return: Early Exit
return-from: Early Exit
returning: Early Exit
returning-from: Early Exit

S
seq: Sequence Iteration
seq-index: Sequence Index Iteration
seq-ref: Sequence Reference Iteration
seqing: Sequence Iteration
seqing-index: Sequence Index Iteration
seqing-ref: Sequence Reference Iteration
sequence: Sequence Iteration
sequence-index: Sequence Index Iteration
sequence-ref: Sequence Reference Iteration
sequencing: Sequence Iteration
sequencing-index: Sequence Index Iteration
sequencing-ref: Sequence Reference Iteration
set: Generic Evaluation
set-accum: Generic Accumulation
set-prev: Generic Evaluation
setting: Generic Evaluation
setting-accum: Generic Accumulation
setting-prev: Generic Evaluation
skip: Skipping Cycles
skip-from: Skipping Cycles
skipping: Skipping Cycles
skipping-from: Skipping Cycles
stream: Sequence Iteration
streaming: Sequence Iteration
string: Sequence Iteration
string-index: Sequence Index Iteration
string-ref: Sequence Reference Iteration
stringing: Sequence Iteration
stringing-index: Sequence Index Iteration
stringing-ref: Sequence Reference Iteration
substream: Sequence Iteration
substreaming: Sequence Iteration
sum: Numeric Accumulation
summing: Numeric Accumulation

T
thereis: Checking Conditions

U
union: Sequence Accumulation
unioning: Sequence Accumulation
unless: Conditionals
until: Early Exit

V
vconcat: Sequence Accumulation
vconcating: Sequence Accumulation

W
when: Conditionals
while: Early Exit
with: Special Macro Arguments
without: Special Macro Arguments
wrap: Special Macro Arguments

Jump to:   A   B   C   D   E   F   I   L   M   N   O   P   R   S   T   U   V   W  

13 Variable Index

Jump to:   L  
Index Entry  Section

L
loopy--accumulation-vars: Background Info
loopy--after-do: Background Info
loopy--at-instructions: Background Info
loopy--before-do: Background Info
loopy--final-do: Background Info
loopy--final-return: Background Info
loopy--generalized-vars: Background Info
loopy--implicit-return: Background Info
loopy--iteration-vars: Background Info
loopy--latter-body: Background Info
loopy--loop-name: Background Info
loopy--main-body: Background Info
loopy--other-vars: Background Info
loopy--post-conditions: Background Info
loopy--pre-conditions: Background Info
loopy--vars-final-updates: Background Info
loopy-aliases: Custom Aliases
loopy-command-parsers: Hello World
loopy-default-flags: Using Flags
loopy-iter--non-main-body-instructions: Custom commands in the loopy-iter macro
loopy-iter-bare-commands: The loopy-iter Macro
loopy-iter-bare-special-macro-arguments: The loopy-iter Macro
loopy-iter-keywords: The loopy-iter Macro
loopy-iter-overwritten-command-parsers: Custom commands in the loopy-iter macro
loopy-result: Accumulation

Jump to:   L  

14 Concept Index

Jump to:   &   _  
A   C   D   F   I   K   L   O   P   S   T   V  
Index Entry  Section

&
&key: Basic Destructuring
&keys: Basic Destructuring
&map: Basic Destructuring
&optional: Basic Destructuring
&rest: Basic Destructuring
&whole: Basic Destructuring

_
_: Basic Destructuring

A
accumulation compatibility: Accumulation
accumulation destructuring: Accumulation
accumulation initial values: Accumulation
accumulation keyword arguments: Common Properties of Accumulation Commands
accumulation keyword at: Common Properties of Accumulation Commands
accumulation keyword into: Common Properties of Accumulation Commands
accumulation keyword key: Common Properties of Accumulation Commands
accumulation keyword test: Common Properties of Accumulation Commands

C
custom aliases: Custom Aliases

D
dash flag: Using Flags
default flag: Using Flags
destructuring macros: Destructuring Macros

F
flag: Using Flags

I
implied accumulation results: Accumulation
instruction order: Background Info
instruction, instructions: Background Info

K
keyword evaluation: Loop Commands

L
loop command: Loop Commands
loopy-dash: Using Flags
loopy-iter: The loopy-iter Macro
loopy-iter bare commands: The loopy-iter Macro
loopy-iter keywords: The loopy-iter Macro
loopy-iter name conflicts: The loopy-iter Macro
loopy-iter sub-loops: The loopy-iter Macro
loopy-pcase: Using Flags
loopy-seq: Using Flags

O
optimizing accumulations: Optimizing Accumulations

P
pcase flag: Using Flags

S
seq flag: Using Flags
sequence element distribution: Sequence Iteration
special macro argument: Special Macro Arguments

T
template variables: Background Info

V
variable destructuring: Basic Destructuring

Jump to:   &   _  
A   C   D   F   I   K   L   O   P   S   T   V  

Footnotes

(1)

Strings being a kind of array. See (elisp)Sequences Arrays Vectors for more.

(2)

https://github.com/magnars/dash.el

(3)

https://common-lisp.net/project/iterate/