#+title: Loopy: A Looping and Iteration Macro
# Make sure to export all headings as such.  Otherwise, some links to
# sub-headings won’t work.
#+options: H:6
# Some parsers require this option to export footnotes.
#+options: f:t

# Badges
[[https://elpa.nongnu.org/nongnu/loopy.html][file:https://elpa.nongnu.org/nongnu/loopy.svg]]
[[https://elpa.nongnu.org/nongnu-devel/loopy.html][file:https://elpa.nongnu.org/nongnu-devel/loopy.svg]]
[[https://melpa.org/#/loopy][file:https://melpa.org/packages/loopy-badge.svg]]
[[https://stable.melpa.org/#/loopy][file:https://stable.melpa.org/packages/loopy-badge.svg]]

-----

~loopy~ is a macro meant for iterating and looping.  It is similar in usage to
[[https://www.gnu.org/software/emacs/manual/html_node/cl/Loop-Facility.html#Loop-Facility][~cl-loop~]] but uses symbolic expressions rather than keywords.

For most use cases, ~loopy~ should be a nice substitute for ~cl-loop~ and
complementary to the features provided by the [[https://www.gnu.org/software/emacs/manual/html_node/elisp/Sequences-Arrays-Vectors.html][Seq]] and [[https://www.gnu.org/software/emacs/manual/html_node/cl/index.html][CL]] libraries and Emacs's
regular looping and mapping features.

For detailed information, see [[file:doc/loopy-doc.org][the documentation file]].  This README is just an
overview.

See also the extension package [[https://codeberg.org/okamsn/loopy-dash][Loopy Dash]], which  provides destructuring using
the library =dash.el= and is described in more detail below.

-----
#+begin_center
*NOTE*: Loopy is still in its latter middle stages.\\
Constructive criticism is welcome.  If you see a place for improvement,
please let me know.
#+end_center
-----

 _Recent breaking changes:_
 - Unreleased:
   - The deprecated variables ~loopy-command-parsers~ and ~loopy-aliases~
     were removed.  Use ~loopy-parsers~ instead.
   - ~loopy-iter-bare-special-macro-arguments~ and ~loopy-iter-bare-commands~
     were removed. Used ~loopy-iter-bare-names~ instead.
 - Version 0.16.0:
   - Using accumulation commands with different initial values for the same
     variable now signals an error instead of a warning.
   - Aliases made obsolete in version 0.14.0 have been removed.  They can still
     be added manually to ~loopy-parsers~.
   - =while= and =until= now signal a warning when receiving multiple
     conditions.  In the future, this will be an error.
 - Version 0.15.0:
   - Loopy now requires at least Emacs version 28.1, increased from version
     27.1.
   - =set= now warns when it is not given a value.  In the future, it will
     signal an error.
   - ~loopy-command-parsers~ and ~loopy-aliases~ are deprecated in favor of
     a single hash table in the new user option ~loopy-parsers~.  This
     simplified the code for adding the =override= special marco argument.
   - ~loopy-iter-bare-special-macro-arguments~ and ~loopy-iter-bare-commands~
     are deprecated in favor of the single variable ~loopy-iter-bare-names~.
   - =when= and =unless= are now implemented separately, fixing when the
     commands are aliased.
   - Modifications to an implied ~loopy-result~ in =finally-do= will now be
     included in the implied return value of the macro.
   - ~loopy-default-flags~ is made obsolete to avoid conflicts between
     libraries.  Using a wrapping macro instead, such as the one given in
     Changelog or the Org/Info documentation.
 - See the [[https://codeberg.org/okamsn/loopy/src/branch/master/CHANGELOG.md][change log]] for less recent changes.

# This auto-generated by toc-org.
* Table of Contents                                                :TOC:noexport:
- [[#introduction][Introduction]]
- [[#similar-libraries][Similar Libraries]]
- [[#how-to-install][How to Install]]
- [[#multiple-kinds-of-destructuring][Multiple Kinds of Destructuring]]
- [[#loop-commands-in-arbitrary-code][Loop Commands in Arbitrary Code]]
- [[#adding-custom-commands][Adding Custom Commands]]
- [[#comparing-to-cl-loop][Comparing to =cl-loop=]]
- [[#real-world-examples][Real-World Examples]]

* Introduction
:PROPERTIES:
:CUSTOM_ID: introduction
:END:

The ~loopy~ macro is used to generate code for a loop, similar to ~cl-loop~.
Unlike ~cl-loop~, ~loopy~ uses parenthetical expressions instead of "clauses".

#+begin_src emacs-lisp
  ;; 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))
#+end_src

~loopy~ supports destructuring for iteration commands like =list= and
accumulation commands like =sum= or =collect=.

#+begin_src emacs-lisp
  ;; Summing the nth elements of sub-arrays:
  ;; => (8 10 12 14 16 18)
  (loopy (list list-elem '(([1 2 3] [4 5 6])
                           ([7 8 9] [10 11 12])))
         (sum ([sum1 sum2 sum3] [sum4 sum5 sum6])
              list-elem)
         (finally-return sum1 sum2 sum3 sum4 sum5 sum6))

  ;; Separate the elements of sub-list:
  ;; => ((1 3) (2 4))
  (loopy (list i '((1 2) (3 4)))
         (collect (elem1 elem2) i)
         (finally-return elem1 elem2))
#+end_src

The ~loopy~ macro is configurable and extensible.  In addition to writing one's
own "loop commands" (such as =list= in the example above), by using "flags", one
can choose whether to instead use destructuring via the libraries =pcase.el=,
=seq.el=, or even =dash.el=.

#+begin_src emacs-lisp
  ;; Use `pcase' to destructure array elements:
  ;; => ((1 2 3 4) (10 12 14) (11 13 15))
  (loopy (flag pcase)
         (array (or `(,car . ,cdr) digit)
                [1 (10 . 11) 2 (12 . 13) 3 4 (14 . 15)])
         (if digit
             (collect digits digit)
           (collect cars car)
           (collect cdrs cdr))
         (finally-return digits cars cdrs))

  ;; Using the default destructuring:
  ;; => ((1 2 3 4) (10 12 14) (11 13 15))
  (loopy (array elem [1 (10 . 11) 2 (12 . 13) 3 4 (14 . 15)])
         (if (numberp elem)
             (collect digits elem)
           (collect (cars . cdrs) elem))
         (finally-return digits cars cdrs))
#+end_src

Variables like =cars=, =cdrs=, and =digits= in the example above are
automatically ~let~-bound so as to not affect code outside of the loop.

~loopy~ has arguments for binding (or not binding) variables, executing code
before or after the loop, executing code only if the loop completes, and for
setting the macro's return value (default: ~nil~).  This is in addition to the
looping features themselves.

All of this makes ~loopy~ a useful and convenient choice for looping and
iteration.

* Similar Libraries
:PROPERTIES:
:CUSTOM_ID: similar-libraries
:END:

Loopy is not the only Lisp library that uses parenthetical expressions instead of
keyword clauses (as in ~cl-loop~).  [[https://common-lisp.net/project/iterate/][Iterate]] and [[https://github.com/Shinmera/for/][For]] are two examples from
Common Lisp.

#+begin_src emacs-lisp
  ;; Collecting 10 random numbers:

  ;; cl-loop (Emacs Lisp)
  (cl-loop repeat 10 collect (random 10))

  ;; loopy (Loopy)
  (loopy (repeat 10) (collect (random 10)))

  ;; iterate (Common Lisp)
  (iterate (repeat 10) (collect (random 10)))

  ;; for (Common Lisp)
  (for:for ((i repeat 10) (randoms collecting (random 10))))

#+end_src

Generally, all of the packages handle basic use cases in similar ways.  One
large difference is that ~iterate~ can embed its looping constructs in arbitrary
code.  Loopy currently provides this feature as a separate macro,
~loopy-iter~, which expands looping constructs using ~macroexpand~
(see [[#loop-commands-in-arbitrary-code][Loop Commands in Arbitrary Code]] in this README).

Loopy is not yet feature complete.  Please request features or report problems
in this project’s [[https://codeberg.org/okamsn/loopy/issues][issues tracker]].  While basic uses are covered, some of the
more niche features of ~cl-loop~ and ~iterate~ are still being added.

* How to Install
:PROPERTIES:
:CUSTOM_ID: how-to-install
:END:

Loopy can be installed from [[https://elpa.nongnu.org/nongnu/loopy.html][Non-GNU ELPA]] and [[https://melpa.org/#/loopy][MELPA]] as the package =loopy=.  The
optional package =loopy-dash= can be installed to enable using the Dash library
for destructuring (instead of other methods).

#+begin_src emacs-lisp
  (use-package loopy)

  ;; Optional support for destructuring with Dash.
  (use-package loopy-dash
    :after (loopy)
    :demand t)
#+end_src

To load all of the alternative destructuring libraries (see section [[#multiple-kinds-of-destructuring][Multiple
Kinds of Destructuring]]) and the alternative macro form (see section [[#loop-commands-in-arbitrary-code][Loop
Commands in Arbitrary Code]]), use

#+begin_src emacs-lisp
  (use-package loopy
    :config
    (require 'loopy-iter)
    (require 'loopy-pcase)
    (require 'loopy-seq))

  (use-package loopy-dash
    :after (loopy)
    :demand t)
#+end_src

* Multiple Kinds of Destructuring
:PROPERTIES:
:CUSTOM_ID: multiple-kinds-of-destructuring
:END:

The default destructuring system is a super-set of what =cl-lib= provides
and is described in the section [[https://codeberg.org/okamsn/loopy/src/branch/master/doc/loopy-doc.org#basic-destructuring][Basic Destructuring]] in the documentation.

In addition to the built-in destructuring style, ~loopy~ can optionally use
destructuring provided by ~pcase-let~, ~seq-let~, and the =dash= library.  This
provides greater flexibility and allows you to use destructuring patterns that
you're already familiar with.

These features can be enabled with "flags", described in the section [[https://codeberg.org/okamsn/loopy/src/branch/master/doc/loopy-doc.org#using-flags][Using Flags]]
in the documentation.

Here are a few examples that demonstrate how ~loopy~ can use destructuring with
accumulation commands.

#+begin_src emacs-lisp
  (require 'loopy-dash)
  ;; => (((1 (2 3)) (4 (5 6))) ; whole
  ;;     (1 4)                 ; i
  ;;     (3 6))                ; k
  (loopy (flag dash)
         (list elem '((1 (2 3)) (4 (5 6))))
         (collect (whole &as i (_ k)) elem)
         (finally-return whole i k))

  ;; = > ((3 5) (4 6))
  (loopy (flag dash)
         (list (&plist :a a  :b b)
               '((:a 3  :b 4 :c 7) (:g 8 :a 5 :b 6)))
         (collect a-vals a)
         (collect b-vals b)
         (finally-return a-vals b-vals))

  (require 'loopy-pcase)
  ;; => ((1 4) (3 6))
  (loopy (flag pcase)
         (list elem '((1 (2 3)) (4 (5 6))))
         (collect `(,a (,_ ,b)) elem)
         (finally-return a b))

  ;; => ((1 6) (3 8) ([4 5] [9 10]))
  (require 'loopy-seq)
  (loopy (flag seq)
         (list elem '([1 2 3 4 5] [6 7 8 9 10]))
         (collect [a _ b &rest c] elem)
         (finally-return a b c))
#+end_src

For more on how =dash= does destructuring, see their documentation on the [[https://github.com/magnars/dash.el#-let-varlist-rest-body][-let]]
expression.

* Loop Commands in Arbitrary Code
:PROPERTIES:
:CUSTOM_ID: loop-commands-in-arbitrary-code
:END:

The macro ~loopy-iter~ can be used to embed loop commands in arbitrary code.  It
is similar in use to Common Lisp's Iterate macro, but it is not a port of
Iterate to Emacs Lisp.

#+begin_src emacs-lisp
  (require 'loopy-iter)

  ;; => ((1 2 3) (-3 -2 -1) (0))
  ;; Things to node:
  ;; - `accum-opt' produces more efficient accumulations for names variables
  ;; - `cycling' is another name for `repeat'
  ;; => ((-9 -8 -7 -6 -5 -4 -3 -2 -1)
  ;;     (0)
  ;;     (1 2 3 4 5 6 7 8 9 10 11))
  (loopy-iter (accum-opt positives negatives zeroes)
              (numbering i :from -10 :to 10)
              ;; Normal `let' and `pcase', not Loopy constructs:
              (let ((var (1+ i)))
                (pcase var
                  ((pred cl-plusp)  (collecting positives var))
                  ((pred cl-minusp) (collecting negatives var))
                  ((pred zerop)     (collecting zeroes var))))
              (finally-return negatives zeroes positives))

  ;; => 6
  (loopy-iter (listing elem '(1 2 3))
              (funcall #'(lambda (x) (summing x))
                       elem))
#+end_src

For more on this, [[https://codeberg.org/okamsn/loopy/src/branch/master/doc/loopy-doc.org#the-loopy-iter-macro][see the documentation]].

* Adding Custom Commands
:PROPERTIES:
:CUSTOM_ID: adding-custom-commands
:END:

It is easy to create custom commands for Loopy.  To see how, see the section
[[https://codeberg.org/okamsn/loopy/src/branch/master/doc/loopy-doc.org#custom-commands][Custom Commands]] in the documentation.

* Comparing to =cl-loop=
:PROPERTIES:
:CUSTOM_ID: how-does-it-compare-to-other-approaches
:END:

See the documentation page [[https://codeberg.org/okamsn/loopy/src/branch/master/doc/loopy-doc.org#comparing-to-cl-loop][Comparing to ~cl-loop~]].  See also the wiki page [[https://codeberg.org/okamsn/loopy/wiki/Speed-Comparisons][Speed
Comparisons]].


* Real-World Examples

See the wiki page [[https://codeberg.org/okamsn/loopy/wiki/Examples][Examples]].

# Local Variables:
# org-link-file-path-type: relative
# org-adapt-indentation: nil
# End:
