Lists and anonymous functions

Lists are the essential datatype of Scheme. There is good reason why the name of its ancestor, Lisp, stands for “List Processing”. Typical Scheme programs are largely made of all sorts of manipulations on lists.

Constructing lists

A first way to build a list is to call the list function, giving it the list elements as arguments.

(list 0 1 2 3 4 "Hello")
 (0 1 2 3 4 "Hello")

List elements can have different types (in the example above, there are some integers and one string). The interpreter displays the list between parentheses with elements separated by spaces, a notation which is already familiar.

There is another way, using “quoting”, which is explained more in detail in the next part of this tutorial. The syntax is:

'(element1 element2 element3 ...)

Put differently, just write the list elements between parentheses, and add a single straight quote ' before them. This syntax is very common in LilyPond:

\shape #'((0 . 0) (0.1 . 0.3) (0.1 . 0.5) (0 . 1.0)) Slur

If you display a list in the sandbox, it will be shown in parentheses, but without the quote. You will soon understand the reason.

'(0 1 2 3 4 "Hello")
 (0 1 2 3 4 "Hello")

Basic operations

The length function computes the length of a list:

(length '(0 1 2 3 "Hello"))
 5

The reverse function returns the list in reverse. This operation is more important than you might think.

(reverse '(0 1 2 3 "Hello"))
 ("Hello" 3 2 1 0)

The list-ref function takes a list and an integer. It returns the list element at the given index. Beware, the first element is at index 0.

(list-ref '(1 2 3 4)
          2)
 3

However, list-ref is actually not so useful. One could describe it as a blind person who would need to open a book at page 267. Since the person cannot see page numbers, which would allow them to browse through the pages to find number 267 quickly, they have no choice but to start at the very beginning and to turn the pages one by one, counting them until 267. list-ref is an inefficient operation. Never do an iteration along the lines of “for i going from 0 to the length of the list minus one, do something with (list-ref the-list i)”. Putting yourself in the blind person’s shoes, it is as if, instead of just turning each page of the Braille book after having read it, you started from the very beginning to count pages again, one by one, until the one after the one you just read. Obviously, this is very inefficient. Fortunately, there are better ways of working with lists, which just “turn the pages one by one”.

Traversing a list

The map function applies the same transformation to each element of a list. This transformation is passed as a function.

(map function lst)

For example:

(define (double x)
  (* 2 x))

(map double '(0 1 2 3))
 (0 2 4 6)

The double function is applied to each element of the list (0 1 2 3), and map returns a list of the results.

It is even possible to give several lists to map. The function must them accept as many arguments as there are lists. The function is first applied with arguments coming from the first elements of the lists, then the second elements, etc.

(define (birthday person date)
  (format #f "~a has their birthday on ~a.\n" person date))

(map birthday
     '("Salima" "John" "Samir")
     '("January 13" "November 9" "February 30"))
 '("Salima has their birthday on January 13." "John has their birthday on November 9." "Samir has their birthday on February 30.")

In this example, the successive calls are

(birthday "Salima" "January 13")
(birthday "John" "November 9")
(birthday "Samir" "February 30")

for-each is like map, but the result of applying the function is discarded. It can be used instead of map if the function has no interesting return value, such as when it returns *unspecified* (map would then return a list full of *unspecified*, which is harmless, but useless).

(define (birthday person date)
  (format #t "~a has their birthday on ~a.\n" person date))

(for-each birthday
          '("Salima" "John" "Samir")
          '("January 13" "November 9" "February 30"))
 Salima has their birthday on January 13.
 John has their birthday on November 9.
 Samir has their birthday on February 30.

Filtering lists

The filter function traverses a list, and returns a new list where only the elements satisfying a certain condition have been kept. The condition is given as a function. For example (the even? function tells whether an integer is even):

(filter even? '(0 1 2 3))
 (0 2)

Using anonymous functions

All the examples above required to define a function separately. In the middle of a large function, when doing lots of such operations, it would be tedious to define separate functions with define. The lambda keyword is used in those cases. lambda defines an anonymous function, which does not need to be given a name as it would need with define.

(lambda (parameter1 parameter2 ...)
  ...)

Thus,

(define (function parameter1 parameter2 ...)
  ...)

is merely a shorthand for

(define function
        (lambda (parameter1 parameter2 ...)
          ...))

The advantage of lambda is that the function can be passed to another function, without giving it a name. Here is how to rewrite the birthday code with lambda:

(for-each
 (lambda (person date)
   (format #f "~a has their birthday on ~a." person date))
 '("Salima" "John" "Samir")
 '("January 13" "November 9" "February 30"))
 Salima has their birthday on January 13.
 John has their birthday on November 9.
 Samir has their birthday on February 30.