https://github.com/okamsn/loopy
A better looping and iteration macro for Emacs.
https://github.com/okamsn/loopy
elisp emacs emacs-lisp
Last synced: 12 months ago
JSON representation
A better looping and iteration macro for Emacs.
- Host: GitHub
- URL: https://github.com/okamsn/loopy
- Owner: okamsn
- License: gpl-3.0
- Created: 2020-11-01T12:22:08.000Z (over 5 years ago)
- Default Branch: master
- Last Pushed: 2024-11-22T02:20:51.000Z (over 1 year ago)
- Last Synced: 2024-11-22T03:17:46.808Z (over 1 year ago)
- Topics: elisp, emacs, emacs-lisp
- Language: Emacs Lisp
- Homepage:
- Size: 1.88 MB
- Stars: 23
- Watchers: 3
- Forks: 3
- Open Issues: 28
-
Metadata Files:
- Readme: README.org
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
#+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://github.com/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:
- =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 and will make adding local overrides easier.
- =when= and =unless= are now implemented separately, fixing when the
commands are aliased.
- Version 0.14.0:
- Conflicting initialization values for accumulation variables now signal
a warning. In the future, they will signal an error.
- The positional arguments to the =numbers= command have been removed,
being deprecated since version 0.12.0.
- Some built-in aliases have been made obsolete and will be removed from the
list of built-in aliases in the future. They can still be added to the
list of known aliases using ~loopy-defalias~. See the changelog for more
information.
- =seq= and =seq-ref= now work on generic sequences and are separate commands
from =sequence= and =sequence-ref=. =sequence-index= now uses
~seq-length~.
- Improved consistency of some keyword arguments:
- The =:unique= keyword argument of the =map= and =map-ref= commands can now
be evaluable at run time.
- The =:close= argument of the =iter= command is now evaluable, instead of
only being used during macro expansion.
- The =:close= argument of the =iter= command is now evaluated at the
beginning of the loop.
- The =:on-failure= argument of the =find= command is now evaluated at the
beginning of the loop.
- Changed back to the old, slightly slower behavior of always initializing
iteration variables to ~nil~, instead of sometimes initializing to the
expected value during the first iteration step. This affects =cons=,
=cycle=, =iter=, =numbers=, =seq-index=, and =substream=.
- See the [[https://github.com/okamsn/loopy/blob/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
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 ~pcase-let~, ~seq-let~, or even the Dash
library for destructuring.
#+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
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://github.com/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
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
The default destructuring system is a super-set of what =cl-lib= provides
and is described in the section [[https://github.com/okamsn/loopy/blob/master/doc/loopy-doc.org#basic-destructuring][Basic Destructuring]] in the documentation.
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://github.com/okamsn/loopy/blob/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
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://github.com/okamsn/loopy/blob/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://github.com/okamsn/loopy/blob/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://github.com/okamsn/loopy/blob/master/doc/loopy-doc.org#comparing-to-cl-loop][Comparing to ~cl-loop~]]. See also the wiki page [[https://github.com/okamsn/loopy/wiki/speed-comparisons][Speed
Comparisons]].
* Real-World Examples
See the wiki page [[https://github.com/okamsn/loopy/wiki/Examples][Examples]].
# Local Variables:
# org-link-file-path-type: relative
# org-adapt-indentation: nil
# End: