Conditions#

Syntaxe if#

Les conditions permettent de prendre des décisions au cours de l’exécution d’un programme, comme « si la température est supérieure à 20 °C, affiche qu’il fait beau ». La syntaxe dédiée est :

(if condition expression-si-oui expression-si-non)

Contrairement à bien des langages où if est une instruction, qui exécute du code si la condition est vérifiée, le if de Scheme est bien une expression. La différence essentielle est qu’une expression s’évalue à une valeur, alors qu’une instruction effectue une action sans renvoyer de valeur. À nouveau, il faut s’entraîner à penser différemment : au lieu de « s’il fait plus de 20 °C, temps prend la valeur beau, sinon, temps prend la valeur gris », on dira plutôt en Scheme : « temps prend la valeur qui est beau s’il fait plus de 20 °C et gris sinon ». La valeur renvoyée par l’ensemble de l’expression if est celle de expression-si-oui si la condition est vraie, et sinon, la valeur de expression-si-non. Prenons un premier exemple :

(define (égaux-ou-différents x y)
  (display
    (if (= x y)
        "x et y sont égaux."
        "x et y sont différents."))
  (newline))

(égaux-ou-différents 4 4)
 x et y sont égaux.
(égaux-ou-différents 4 5)
 x et y sont différents.

On l’aura compris, la procédure = teste si deux nombres sont égaux. Quelle est sa valeur de retour ?

(= 4 5)
 #f

Le test s’est évalué à #f, qui est la notation du booléen « faux » (false). L’opposé de #f est #t (« vrai », true).

(= 5 5)
 #t

Il est important de remarquer que seule l’une des deux expressions est évaluée, en fonction de la valeur du test. L’exemple précédent pourrait tout aussi bien s’écrire :

(if (= x y)
    (display "x et y sont égaux.")
    (display "x et y sont différents."))

Si \(x \neq y\), l’expression (display "x et y sont égaux.") n’est jamais évaluée, ce qui est heureux, car autrement, on verrait les deux messages s’afficher à l’écran. Un autre intérêt est de permettre des programmes comme :

(define (affiche-inverse x)
  (if (= x 0)
      (display "impossible de diviser par zéro")
      (display (/ 1 x)))
  (newline))

(affiche-inverse 0)
 impossible de diviser par zéro

On affiche ici l’inverse de \(x\) (c’est à dire \(\frac1x\)). Cet inverse n’existe que si \(x \neq 0\), car on ne peut pas diviser par 0. Ce n’est que dans le cas où l’on a bien vérifié que \(x \neq 0\) que l’expression (/ 1 x) s’évalue, ce qui garantit qu’elle ne produira pas d’erreur.

Syntaxe cond#

cond est l’équivalent de ce qui dans d’autres langages s’écrit if ... else if ... else if ..., c’est à dire « si… sinon, si…, sinon, si… ». Cette syntaxe est un moyen pratique d’écrire du code qui doit distinguer entre de nombreux cas. Prenons l’exemple d’une tête de note que LilyPond doit afficher en fonction de son style. On pourrait écrire un code dans le goût de :

(if (le-style-est-default)
    (afficher-la-tête-default)
    ; Sinon...
    (if (le-style-est-altdefault)
        (afficher-la-tête-altdefault)
        ; Sinon...
        (if (le-style-est-baroque)
            (afficher-la-tête-baroque)
            ; Sinon...
            (if (le-style-est-mensural)
                (afficher-la-tête-mensural)
                ; Sinon...
                (if (le-style-est-petrucci)
                    (afficher-la-tête-petrucci)
                    ; Sinon...
                    etc. etc. etc.)))))

Bien sûr, ceci est très lourd et les parenthèses pullulent. La syntaxe cond vient à notre secours. Elle se présente comme ceci :

(cond
  (condition1 expression1)
  (condition2 expression2)
  (condition3 expression3)
  ...)

Les conditions sont évaluées dans l’ordre. Dès qu’une condition s’évalue à #t, l’expression correspondante est évaluée et l’expression cond entière prend sa valeur.

À la place de la dernière condition, on peut écrire else, ce qui revient au même que #t. La dernière expression est alors toujours évaluée si aucune des conditions n’est vraie.

Un exemple avec cond :

(define (category temperature)
  (cond
   ((> temperature 50)
    "unbearable temperature")
   ((> temperature 40)
    "hardly bearable temperature")
   ((> temperature 30)
    "hot to very hot temperature")
   ((> temperature 20)
    "moderate temperature")
   ((> temperature 10)
    "rather cold temperature")
   ((> temperature 0)
    "cold temperature")
   ((> temperature -10)
    "very cold temperature")
   (else
    "polar temperature")))

(display (category 28))
 moderate temperature

Tests courants#

Le premier test que nous avons rencontré était =. Il s’applique exclusivement à des nombres. Il teste leur égalité numérique, c’est à dire qu’il ne prend pas en compte le type de nombre, entier ou à virgule.

(= 3 3.0)
 #t

Un autre test d’égalité courant est equal?. Il s’applique à tous les types, et non pas seulement aux nombres. On peut par exemple s’en servir pour comparer des chaînes de caractères.

(equal? "Bonjour" "Bonjour")
 #t

Même sur des nombres, = et equal? ne reviennent pas au même. En effet, equal? ne considère pas que des nombres de types différents soient égaux.

(equal? 3 3.0)
 #f

Le point d’interrogation fait partie du nom de equal? et ne pose pas de problème syntaxique (relire Définition de variables). Il est souvent mis à la fin des noms de procédures qui font un test, c’est à dire qu’elles renvoient vrai ou faux. On appelle ces procédures des prédicats.

Pour comparer des nombres, on dispose des procédures <, <=, > et >=, qui se lisent « strictement inférieur », « inférieur ou égal », « strictement supérieur » et « supérieur ou égal ». Ce sont les équivalents des symboles mathématiques \(<\), \(\leq\), \(>\) et \(\geq\).

(>= 4 3)
 #t
(>= 3 4)
 #f
(> 3 3)
 #f
(>= 3 3)
 #t

Combiner des tests#

Deux opérateurs logiques principaux permettent de combiner les tests ensemble :

  • and, le ET logique, qui vérifie si toutes les conditions sont vraies ;

  • or, le OU logique, qui teste si au moins l’une des conditions est vraie.

(define (GPS lieu)
  (if (or (equal? lieu "Pôle Nord")
          (equal? lieu "Pôle Sud"))
      (display "Enfilez les manteaux !")
      (display "Pas encore arrivés au pôle."))
  (newline))

(GPS "Pôle Nord")
 Enfilez les manteaux !
(GPS "Helsinki")
 Pas encore arrivés au pôle.
(define (détecte-requiem compositeur œuvre)
  (cond
    ((and (equal? œuvre "Requiem")
          (equal? compositeur "Mozart"))
     (display "Le requiem de Mozart"))
    ((equal? œuvre "Requiem")
     (display "Un certain requiem (pas de Mozart)"))
    (else
     (display "Pas un requiem")))
  (newline))

(détecte-requiem "Mozart" "Requiem")
 Le requiem de Mozart.

L’opérateur not est un NON logique, qui inverse un test.

(define (conversation est-musicien logiciel-préféré)
  (cond
    ((not est-musicien)
     (display "Quel dommage pour vous !"))
    ((not (equal? logiciel-préféré "LilyPond"))
     (display "Connaissez-vous un logiciel formidable appelé LilyPond ?"))
    (else
     (display "À quand votre première contribution ?")))
  (newline))

(conversation #t "LilyPond")
 À quand votre première contribution ?

« if manchot »#

Il arrive assez souvent qu’il n’y ait rien à faire si une condition est fausse. Ceci est particulièrement vrai pour les messages d’erreur. Il existe pour ces cas une deuxième forme de l’instruction if, où l’on omet tout simplement l’expression à évaluer quand la condition est fausse :

(if condition expression-si-oui)

Par exemple :

(define (vérifie-compositeur nom)
  (if (equal? nom "inconnu")
      (error "compositeur non trouvé dans la base de données")))

(vérifie-compositeur "inconnu")

Si la condition est vraie, expression-si-oui est évaluée et le if entier prend cette valeur, comme dans les if habituels. Si la condition est fausse, l’expression n’a aucune valeur intéressante. En fait, rien ne s’affiche dans l’invite interactive :

guile> (if #t "valeur si oui")
"valeur si oui"
guile> (if #f "valeur si oui")
guile>

Mais l’expression a tout de même une valeur !

guile> *unspecified*
guile> (equal? *unspecified* (if #f "valeur si oui"))
#t

Cette valeur est *unspecified*, une constante spéciale renvoyée par toutes les fonctions qui n’ont besoin de renvoyer aucune valeur particulière. La fonction display renvoie également *unspecified*. Pour ne pas alourdir la sortie, le bac à sable ignore tout simplement *unspecified*.

De la vérité universelle#

Les if n’acceptent pas seulement des booléens comme conditions. Dans d’autres langages, certains types possèdent des règles spécifiques quant à la valeur de vérité. Par exemple, les nombres sont généralement considérés comme vrais, sauf 0. De même, les chaînes de caractères sont vraies, sauf les chaînes vides, et les listes sont vraies, sauf les listes vides.

En Scheme, les choses sont un peu différentes. Absolument toutes les valeurs sont vraies, sauf le booléen #f. En particulier, 0 et les chaînes vides sont vrais.

(if 0 "oui" "non")
 "oui"

C’est pourquoi on prend généralement #f pour représenter les valeurs manquantes, non trouvées, etc. Avec cette convention, la fonction vérifie-compositeur se réécrit :

(define (vérifie-compositeur nom) ; nom est une chaîne de caractères ou #f
  (if (not nom) ; détecte #f
      (error "compositeur non trouvé dans la base de données")))

De même que if, and et or n’évaluent leurs arguments que lorsque cela s’avère nécessaire. De plus, ils ne renvoient pas forcément un booléen. Si toutes les expressions sont vraies, and renvoie la valeur de la dernière. Si au moins une des expressions est vraie, or renvoie la valeur de la première expression vraie. Cette propriété est à la base d’une astuce fréquente qui consiste à abuser de or pour définir des valeurs par défaut.

(define (affiche-compositeur compositeur)
  (display
    (or compositeur "[inconnu]"))
  (newline))

(affiche-compositeur "Mozart")
 Mozart
(affiche-compositeur #f)
 [inconnu]

Décortiquons ce qui se produit. Si compositeur est une valeur vraie (n’importe quel objet sauf #f), le test (or compositeur "[inconnu]") peut s’arrêter, puisque or a déjà trouvé une valeur vraie (il en faut au moins une). or renvoie alors cette valeur, qui est compositeur. Si compositeur vaut #f, alors le test or ne peut pas s’arrêter là et évalue l’expression suivante, qui est "[inconnu]". Cette expression est vraie, donc il termine et la renvoie.