Loops in Emacs Lisp

0
4728

This article in the Emacs series explores looping techniques that are available with Emacs Lisp.

T here are built-in constructs such as while and dolist that are shipped with the default GNU Emacs. The dash.el library provides functions to iterate over lists, and is written by Magnar Sveen. The latest release of dash.el is v2.16.0, and its source code is available at https://github.com/magnars/dash.el under the GNU General Public License v3.0.
Let us also explore the structures available in loop.el, another library for implementing imperative loops. This has been written by Wilfred Hughes, and has also been released under the GNU General Public License v3.0.

Installation
The dash.el and loop.el packages are available in Milkypostman’s Emacs Lisp Package Archive (MELPA) and in the Marmalede repo. You can install the package using the following commands in GNU Emacs:

M-x package-install dash
M-x package-install loop

The other method of installation is to copy the dash.el and loop.el source files to your Emacs load path and load them. In order to get syntax highlighting of dash functions in Emacs buffers, you can add the following command to your Emacs initialisation settings:

(eval-after-load 'dash '(dash-enable-font-lock))

If you are using Cask (https://github.com/cask/cask) to manage your Emacs configuration, then you can simply add the following code to your Cask file:

(depends-on "dash")
(depends-on "loop")

The usage of various loop construct is as follows.

Built-in
GNU Emacs has built-in loop constructs. The while function, for example, has the following syntax:

(while TEST BODY...)

The BODY code segment is evaluated if the result of TEST is not nil. Until TEST returns nil, the BODY will continue to be executed. An example of while function usage is given below:

(setq alphabets '(a b c d e))

(defun print-list-elements (list)
"Print each element of the input LIST"
(while list
(print (car list))
(setq list (cdr list))))

(print-list-elements alphabets)

The output is as follows:

a
b
c
d
e
nil

The ‘dolist’ macro loops over a list and is also built-in with Emacs. Its definition is as follows:

(dolist (VAR LIST [RESULT]) BODY...)

The VAR argument represents each element in LIST for every iteration in the BODY segment. The value in RESULT is returned by the function, and is optional. By default, a nil is returned. The ‘alphabet’ list elements can be printed using the dolist macro as shown below:

(setq alphabets '(a b c d e))
(dolist (element alphabets)
(print element))

The resultant output is the same.

a
b
c
d
e
nil

dash.el
The dash.el list library provides functions to iterate over lists. The -each function, for example, takes a list and a function, and applies the function to every element in the list. In the following example, a reverse of the input list is created by doubling each element’s value.

(-each list function) ;; Syntax

(let (s) (-each '(1 2 3) (lambda (item) (setq s (cons (* item 2) s)))) s)
(6 4 2)

Another function API from the dash.el library is the -each-while function, which takes three arguments – a list, a predicate and a function. The function is applied to every element in the list that satisfies the predicate. For example:

(-each-while list predicate function) ;; Syntax

(defun even? (num) (= 0 (% num 2)))
(let (s) (--each-while '(1 2 3 4) (< it 3) (!cons it s)) s)
(2 1)

The -each-r function takes a list and a function, and applies the function on every item in the list in the reverse order. An example is given below:

(-each-r list function) ;; Syntax

(let (s) (-each-r '(1 2 3) (lambda (item) (setq s (cons (* item 2) s)))) s)

(2 4 6)

If you would like to use a predicate function with -each-r, you can use the -each-r-while function as illustrated below:

(-each-r-while list predicate function) ;; Syntax

(let (s) (-each-r-while '(1 2 3 4 5 6) 'even? (lambda (item) (!cons item s))) s)
(6)

The -dotimes function will repeatedly call a function from 0 to the input number, minus 1.

(-dotimes number function) ;; Syntax

(let (s) (-dotimes 3 (lambda (n) (!cons n s))) s)
(2 1 0)

You can explore more of the iterative functions available in dash.el under the Side-effects section in the GitHub source repository available at https://github.com/magnars/dash.el.

loop.el
We shall now explore the constructs available in the loop.el library. The loop-while construct executes the body of the loop while the condition is true. In the following example, the sum of the numbers from 0 to 5 is computed.

(require 'loop)

;; loop-while
loop-while (condition body...) ;; Syntax

(let ((x 0)
(sum 0))
;; sum of 0..5
(loop-while (< x 5)
(setq sum (+ sum x))
(setq x (1+ x)))
sum)
10

If you want to evaluate the body at least once before checking the condition, you can use the loop-do-while construct. In the following example, the value of x is incremented by one and then the condition is satisfied for the loop execution.

loop-do-while (condition body...) ;; Syntax

(let ((x 0)
(sum 0))
;; sum of 1..4
(loop-do-while (and (> x 0) (< x 5))
(setq sum (+ sum x))
(setq x (1+ x)))
sum)
10

The loop-until construct repeatedly evaluates the body of the code until the condition becomes true. For example:

loop-until (condition body...) ;; Syntax

(let ((x 0)
(sum 0))
;; sum of 0..4
(loop-until (= x 5)
(setq sum (+ sum x))
(setq x (1+ x)))
sum)
10

The loop-for-each construct takes three arguments – a var, a list and a body. The var represents an element in the list for the iteration. In the following example, the sum of numbers from 1 to 5 is calculated.

loop-for-each (var list body...) ;; Syntax

(let ((sum 0))
(loop-for-each x (list 1 2 3 4 5)
(setq sum (+ sum x)))
sum)
15

A couple of constructs are available to break or continue execution within a loop. The loop-break construct breaks out of the innermost loop. For example:

loop-break () ;; Syntax

(let ((sum 0))
;; sum 1..5
(loop-for-each x (list 1 2 3 4 5 6)
(setq sum (+ sum x))
(when (= x 5)
(loop-break)))
sum)
15

The loop-continue construct will skip the rest of the current loop-while, loop-do-while or loop-for-each block and will proceed to the next iteration in the loop. In the following example, the list is iterated for elements between 1 and 6, and is skipped when the iteration matches the element 2.

loop-continue () ;; Syntax

(let ((sum 0))
;; sum the numbers 1, 3, 5
(loop-for-each x (list 1 2 3 4 5 6)
(when (= x 2)
(loop-continue))
(setq sum (+ sum x)))
sum)
19

The loop.el library has unit tests included in the source code, which you can run to validate the defined constructs. In order to run the tests, you need to first clone the source repository using the following commands:

$ git clone https://github.com/Wilfred/loop.el
Cloning into 'loop.el'...
remote: Enumerating objects: 232, done.
remote: Total 232 (delta 0), reused 0 (delta 0), pack-reused 232
Receiving objects: 100% (232/232), 31.29 KiB | 801.00 KiB/s, done.
Resolving deltas: 100% (117/117), done.

If you do not have Cask, install it using the instructions provided in the README file at https://github.com/cask/cask.
You can then change the directory into the cloned loop.el folder, and run cask install. This will locally install the required dependencies for running the tests.

$ cd loop.el/
$ cask install
Loading package information... Select coding system (default utf-8):
done
Package operations: 3 installs, 0 removals
- Installing [ 1/3] undercover (latest)... done
- Installing [ 2/3] ert-runner (latest)... done
- Installing [ 3/3] f (latest)... already present

A Makefile exists in the top-level directory, the contents of which are provided below for reference:

$ cat Makefile
CASK ?= cask
EMACS ?= emacs

all: test
test: unit

unit:
${CASK} exec ert-runner

install:
${CASK} install

You can now simply run make test at the shell prompt to execute the tests as shown below:

$ make test
cask exec ert-runner
.................

Ran 17 tests in 0.001 seconds

Readers are encouraged to go through the README file at https://github.com/Wilfred/loop.el for more information.

LEAVE A REPLY

Please enter your comment!
Please enter your name here