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.

guile> (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

Opérations de base

La fonction length donne la longueur d’une liste :

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

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"))

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)

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. Fort heureusement, il existe de meilleurs moyens de travailler sur les listes.

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))

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"))

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"))

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))

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 (personne date)
    (format #t "~a fête son anniversaire le ~a.\n" personne date))
  '("Amélie" "Jules" "Jonathan")
  '("13 janvier" "9 novembre" "30 février"))