Listes et fonctions anonymes#

Les listes sont le type de données essentiel de Scheme. Ce n’est pas pour rien que son prédécesseur, Lisp, tire son nom de « List Processing ». Une bonne partie de la programmation Scheme consiste en des opérations sur des listes.

Construction de listes#

Le premier moyen d’obtenir une liste est d’appeler la fonction list avec pour arguments les éléments de la future liste.

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

Les éléments de la liste peuvent être de types différents (ci-dessus, des entiers et une chaîne). L’interpréteur affiche la liste entre parenthèses avec les éléments séparés par des espaces, une notation déjà familière.

Il existe un autre moyen, qui passe par le « quoting », objet de la partie suivante. La syntaxe est :

'(élément1 élément2 élément3 ...)

Autrement dit, il suffit de prendre la liste sous forme d’expression parenthésée, et de rajouter juste devant une apostrophe '. Cette syntaxe est très courante en LilyPond :

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

Si vous affichez une liste dans l’interpréteur interactif, elle se présente entre parenthèses, mais sans l’apostrophe. Vous en comprendrez bientôt la raison.

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

Opérations de base#

La fonction length donne la longueur d’une liste :

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

La fonction reverse renvoie la liste à l’envers. Cette opération est plus importante qu’on ne le croit.

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

La fonction list-ref prend pour arguments une liste et un entier. Elle renvoie l’élément de la liste situé à l’index spécifié. Attention, le premier élément est à l’index 0.

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

Cependant, la fonction list-ref n’est pas si utile. Il faut l’imaginer comme un aveugle qui voudrait ouvrir un livre à la page 267. Comme il ne voit pas les numéros de page qui lui permettraient de feuilleter le livre et s’arrêter à la bonne page, il n’a pas d’autre choix que de commencer au tout début, et de tourner les pages une par une, en les comptant, jusqu’à ce qu’il en ait tourné exactement le bon nombre pour parvenir à la page 267. list-ref est donc une opération inefficace. Il ne faut surtout pas faire une itération du type « pour i de 0 à la longueur de la liste - 1, faire quelque chose avec (list-ref liste i). En se mettant à la place de l’aveugle, c’est comme, après chaque page, recommencer depuis le début et tourner à nouveau les pages une par une jusqu’à en avoir tourné une de plus. Cette itération est donc très inefficace. Fort heureusement, il existe de meilleurs moyens de travailler sur les listes, qui tournent simplement les pages une par une, de manière efficace donc.

Parcours d’une liste#

La fonction map applique une transformation à tous les éléments d’une liste. La transformation est passée à map comme une fonction :

(map fonction liste)

Par exemple :

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

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

La fonction double est appliquée à chaque élément de la liste (0 1 2 3), et map renvoie une nouvelle liste formée des résultats, en l’occurrence (0 2 4 6).

On peut même passer plusieurs listes à map. La fonction devra prendre autant d’arguments que de listes. Elle est d’abord appliquée à tous les premiers éléments, puis tous les deuxièmes éléments, etc.

(define (anniversaire personne date)
  (format #f "~a fête son anniversaire le ~a" personne date))

(map anniversaire
     '("Amélie" "Jules" "Jonathan")
     '("13 janvier" "9 novembre" "30 février"))
 ("Amélie fête son anniversaire le 13 janvier" "Jules fête son anniversaire le 9 novembre" "Jonathan fête son anniversaire le 30 février")

Dans cet exemple, les appels successifs sont

(anniversaire "Amélie" "13 janvier")
(anniversaire "Jules" "9 novembre")
(anniversaire "Jonathan" "30 février")

for-each est comme map, mais le résultat de la fonction n’est pas conservé. On l’utilise à la place de map lorsque la fonction n’a pas de valeur de retour particulière (map renverrait alors une liste de *unspecified*, ce qui n’est pas gênant mais inutile).

(define (anniversaire personne date)
  (format #t "~a fête son anniversaire le ~a.\n" personne date))

(for-each anniversaire
          '("Amélie" "Jules" "Jonathan")
          '("13 janvier" "9 novembre" "30 février"))
 Amélie fête son anniversaire le 13 janvier.
 Jules fête son anniversaire le 9 novembre.
 Jonathan fête son anniversaire le 30 février.

Filtrage d’une liste#

La fonction filter parcourt une liste et ne conserve que les éléments qui satisfont une certaine condition. Celle-ci est passée sous forme d’une fonction. Par exemple (la fonction even? dit si un entier est pair) :

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

Passage d’une fonction anonyme#

Les exemples précédents nécessitaient de définir une fonction à part. Au milieu d’une fonction, pour une petite opération, il deviendrait pénible (et souvent impossible) de définir des fonctions séparées. Le mot-clé lambda permet de définir une fonction anonyme, à laquelle on ne donne pas de nom comme avec define.

(lambda (paramètre1 paramètre2 ...)
  ...)

Ainsi,

(define (fonction paramètre1 paramètre2 ...)
  ...)

n’est rien qu’un raccourci pour

(define fonction
        (lambda (paramètre1 paramètre2 ...)
          ...))

L’intérêt de lambda est que la fonction peut être directement embarquée dans une expression plus grande. Voici comment réécrire le code des anniversaires avec 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.