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 #t "~a has their birthday on ~a.\n" 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.