-
Notifications
You must be signed in to change notification settings - Fork 3
Versus `cl loop`
NOTE: As Loopy develops, these examples and results might become out of date. For the most up-to-date information, refer to this package’s documentation, which is installed with the package as the Info file loopy
.
loopy
should be comparable with cl-loop
for most things, keeping in
mind the following:
- It has more flexible control-flow commands, under which you can easily group sub-commands, including assignments.
- It has a
skip
command to skip the rest of the loop body and immediately start the next iteration. Of course, a similar effect could be achieved using thewhen
orunless
commands.
loopy
is not always one-to-one replacement for cl-loop
, but it is easy to
use and extend, and performs well in the cases that it already handles.
Below is a simple example of loopy
vs cl-loop
.
(require 'cl-lib)
(cl-loop with some-thing = 5
for i from 1 to 100
do (message "I is %s" i)
when (> (+ i 5) 20)
return (format "Done: %d" i))
(require 'loopy)
(loopy (with (some-thing 5))
(list i (number-sequence 1 100))
(do (message "I is %s" i))
(when (> (+ i 5) 20)
(return (format "Done: %d" i))))
The main benefit (I believe) of Loopy is clearer grouping of commands under conditionals while still using a clean syntax, such as in the below example.
;; => '((2 4) (4 8) (6 12) (8 16) (10 20))
(loopy (list i (number-sequence 1 10))
(when (cl-evenp i)
(expr once i)
(expr twice (* 2 i))
(collect together (list once twice)))
(finally-return together))
;; Though you are more likely to write this as:
(loopy (list i (number-sequence 1 10))
(when (cl-evenp i) (collect (list i (* 2 i)))))
In my experience, cl-loop
does not allow the easy grouping of assignment
statements under a when
condition. For example, below is something I would
like to try to do with cl-loop
.
I am aware that in this example the for
statements aren’t necessary and that
the collect
statements would be sufficient, but (when I come across things
like this in my work) I would like to use them to declare variables for
readability purposes.
(require 'cl-lib)
(save-match-data
(cl-loop with pattern = "^Line\\([[:digit:]]\\)-Data\\([[:digit:]]\\)"
for line in (split-string "Line1-Data1\nBad\nLine2-Data2")
when (string-match pattern line)
for line-num = (concat "L" (match-string 1 line))
and for data-num = (concat "D" (match-string 2 line))
;; … Further processing now that data is named …
and collect line-num into line-nums
and collect data-num into data-nums
finally return (list line-nums data-nums)))
;; Normal Elisp:
(save-match-data
(let ((pattern "^Line\\([[:digit:]]\\)-Data\\([[:digit:]]\\)")
(line-nums)
(data-nums))
(dolist (line (split-string "Line1-Data1\nBad\nLine2-Data2"))
(when (string-match pattern line)
(let ((line-num (concat "L" (match-string 1 line)))
(datum-num (concat "D" (match-string 2 line))))
;; … Further processing now that data is named …
(push line-num line-nums)
(push datum-num data-nums))))
(list (nreverse line-nums) (nreverse data-nums))))
Here is how one could currently do it with loopy
:
(require 'loopy)
(save-match-data
;; The flag `split' tells `loopy' to accumulate into separate variables when
;; no variable is given in an accumulation command (instead accumulating into
;; the variable `loopy-result'). This allows for a more efficient code
;; expansion, since `loopy' knows that the variable won't be accessed before
;; the loop ends.
(loopy (flag split)
(with (pattern "^Line\\([[:digit:]]\\)-Data\\([[:digit:]]\\)"))
((list line (split-string "Line1-Data1\nBad\nLine2-Data2"))
(when (string-match pattern line)
(expr line-num (concat "L" (match-string 1 line)))
(expr datum-num (concat "D" (match-string 2 line)))
;; … Further processing now that data is named …
;; If we didn't wish to use the `split' flag, then we would need to
;; specify variables into which to accumulate, such as in
;; (collect line-nums line-num)
;; and
;; (collect datum-nums datum-num)
(collect line-num)
(collect datum-num)))))
I believe that the value of the macro increases for longer loop bodies with several conditional commands.
Another nice ability, one that I’m not sure cl-loop
has, is a specific
command for skipping/continuing a loop iteration. Of course, one could also
re-organize code under a conditional command like when
to achieve the same
effect.
;; Returns even numbers that aren't multiples of 10.
;; => (2 4 6 8 12 14 16 18)
(loopy (list i (number-sequence 1 20))
(when (zerop (mod i 10)) (skip))
(when (cl-evenp i) (collect i)))
See the section Translating ~cl-loop~ in the Org documentation. This section is included in Loopy’s documentation when installed.