Expressions#

Le code Scheme ne se construit pas autour d’instructions mais autour d’expressions. Autrement dit, au lieu de donner des ordres à l’ordinateur (fais ceci, puis cela), on explique comment construire le résultat en combinant des opérations. Voyons comment faire.

Premières opérations#

Revenons sur un exemple déjà rencontré :

(+ 42 5)

Cette syntaxe, fondement absolu de Scheme, est celle d’un appel de fonction, ou, pour employer le terme plus commun en Scheme, d’un appel de procédure. Ce qui, en LilyPond, s’écrit  :

\fonction argument1 argument2 ...

se note en Scheme :

(fonction argument1 argument2 ...)

Attention, les parenthèses ne sont pas facultatives !

(+ 2 2)
 4
+ 2 2
 #<primitive-generic +>
 2
 2

En effet, + 2 2 sans parenthèses est simplement une suite d’expressions, sans aucun lien les unes avec les autres. Le bac à sable les évalue donc à la file et affiche leurs valeurs, comme si l’on avait fait :

+
 #<primitive-generic +>
2
 2
2
 2

Les parenthèses, en plus d’opérer un regroupement, indiquent que le premier élément est une procédure (une fonction) qu’il faut appliquer aux autres. A contrario, il ne faut pas mettre trop de parenthèses.

((+ 2 2))
 ERROR: Wrong type to apply: 4
   ABORT: (misc-error)

Que se passe-t-il ? D’abord, l’expression à l’intérieur des parenthèses est évaluée. La valeur de (+ 2 2) est 4. L’expression devient donc :

(4)

ce qu’il faut voir comme

(4
 ; Il pourrait y avoir d'autres choses ici.
 )

Ainsi, (4) est l’appel de la fonction 4 sans arguments. Mais 4 n’est pas une fonction ! D’où l’erreur « Wrong type to apply » : 4 n’est pas un objet que l’on appelle impunément comme si c’était une fonction.

On réalise de même les autres opérations élémentaires :

(+ 2 2) ; deux plus deux
(- 5 3) ; cinq moins trois
(* 5 6) ; cinq fois six
(/ 6 4) ; six divisé par quatre

Cette notation a de quoi surprendre notre œil familier des écritures habituelles \(2 + 2\), \(5 - 3\), \(5 \times 6\) et \(6 / 4\). Il faut s’habituer à reformuler notre pensée dans cet ordre à la Yoda. Plutôt que « deux plus deux », ou « deux additionné à deux », on dit en Scheme : « l’addition de deux et deux ». De même, « la division de quatre par deux », etc.

Notez au passage que la division sur des entiers donne toujours un résultat exact, éventuellement fractionnaire. Ainsi, (/ 6 4) est simplifié en 3/2 (trois demis).

Les quatre fonctions présentées ici peuvent d’ailleurs prendre plus que deux arguments :

(+ 3 4 5)

Dans d’autres langages, il aurait fallu répéter l’opérateur + dans l’expression, en écrivant 3 + 4 + 5. En Scheme, ce n’est pas nécessaire, car les parenthèses se chargent de délimiter les arguments. (+ 3 4 5) est simplement l’addition des nombres 3, 4 et 5.

Cette syntaxe est si omniprésente que nous l’avons déjà rencontrée :

(display "une chaîne de caractères")
(skip-of-length #{ { c'8 d' e' f' e' d' } #})

display et skip-of-length sont également des procédures, que l’on appelle sur des arguments de la même manière.

Mentionnons également, dans la série des procédures arithmétiques, deux procédures prédéfinies que l’on rencontre souvent  : 1+ et 1-. La première ajoute 1, la deuxième soustrait 1. (Il ne s’agit que de procédures prédéfinies, on ne peut pas faire 2- par exemple.)

(1+ 5)  6 ; équivalent à (+ 5 1)
(1- 5)  4 ; équivalent à (- 5 1)

Attardons-nous enfin sur :

+
 #<primitive-generic +>

Les procédures en Scheme sont des objets comme les autres. Cette représentation entre crochets est donnée par l’interpréteur faute de mieux, puisque l’on imagine mal comment écrire la « valeur » de la procédure. Cela n’empêche pas cette procédure d’exister en propre, de même que 4 est un entier, 42.5 un nombre à virgule et "abcd" une chaîne de caractères. Mais quelle est donc la nature de cet objet « procédure » ? En réalité, la question en programmation n’est pas tant de savoir ce qu’un objet est que ce qu’il fait. Pensons aux nombres. On peut les afficher, les additionner, les soustraire, les multiplier, etc. Les procédures, elles, n’ont qu’une seule opération à offrir, l’appel. Une procédure est tout simplement un objet que l’on peut appeler, ce que l’on réalise avec la syntaxe (procédure argument1 argument2 ...).

Aussi, comme toutes les valeurs, les procédures peuvent être stockées dans des variables. + n’est rien qu’une variable prédéfinie qui contient la procédure standard d’addition. On pourrait faire :

(define addition +)
(addition 2 2)
 4

Imbrication des expressions#

Armés de ces connaissances, attaquons-nous à une expression légèrement plus complexe.

\[(2 \times 3) + (3 \times 5) \]

Pour traduire cette expression en Scheme, il faut commencer par se demander ce qu’elle est, en premier le type d’opération. C’est ici une addition.

(+ ... ...)

Ensuite, on complète : c’est l’addition « de quoi » ? L’addition de \(2 \times 3\) et \(3 \times 5\). On continue de la même manière. \(2 \times 3\) est la multiplication de 2 par 3, et \(3 \times 5\) est la multiplication de 3 par 5. On en déduit l’écriture du calcul :

(+ (* 2 3) (* 3 5))

On a imbriqué des expressions dans des expressions. Essayez de calculer à l’aide du bac à sable la valeur de

\[1 - (5 \times 6) - (3 \times (4 + (3 \times 12))) \]

(vous devriez trouver -149).

Formatage du code#

Ainsi, programmer en Scheme consiste essentiellement à imbriquer des expressions dans des expressions plus grandes. Si vous avez réussi l’exercice précédent, vous aurez remarqué qu’à la fin, on ferme quatre parenthèses à la file. Dans des programmes plus complexes, les choses ne vont pas en s’arrangeant et il n’est pas rare de fermer une dizaine de parenthèses d’un seul coup. Si le nom de Lisp, la famille de langages dont Scheme est issu, signifie « List Processing », les mauvaises langues prétendent qu’il est l’acronyme de « Lots of Insipid and Stupid Parentheses » (Nombreuses Parenthèses Insipides et Idiotes). Les débutants peuvent avoir une image mentale de Scheme qui ressemble à  :

          ()(((((()))))((                    )((())((()(    )())         )(()   ())()((()(((((    ())(     ()()  ())()(()(())((
      )))((())((())))(                  (()())((()))        ))))         )(()   ))()()(()(()(     ()((() ))(((   ((()()(())(()
    ))()))(()))()                   (())((())()(            ))()         ())(   (())              ))   )((       (())
  )))))((((()((              ))()()()))()))                 )(()         )(()   ()((              )((   (  ()(   (())
  )()(((()))(()(             )()(()()()()()(                ((()         ()()   )())              )))   )   ()(  ()))
      )()(()()((())(         (()()(((()))))                 ()))())))(((()()(   ))))())((         ))(       (((  ((((()())
              ))(((())(()((  (())((()))(()))                )(((((()(((()())(   )())              )()       )))  )))(
                )()))()()()  ))()))))((()))                 ()()         (())   ()()              ()(       )((  (())
             )(((())(())         )))()()((()(()(            )))(         ))((   ))((              ())       )()  ()((
          ))((((()()))               )(()(()())))           ()((         ()((   )()(              (((       ))(  (()(
       ()()))((()()()                   ((()())))))())))    ()()          ))()  ()()()(())()      (((       )()  ())()(()())(
((())))(((())(()                            )(()()((())(((  ()()           )()( )))))())((()((   )()        ()(  ())(()(())))))

Pour garder la tête froide, le meilleur moyen est de respecter certaines règles simples de formatage du code qui le rendent lisible. Il existe trois formes possibles pour une expression. La première consiste à placer le nom de la procédure et tous ses arguments sur une seule ligne.

(procédure argument1 argument2 ...)

Dans la deuxième, les arguments sont placés chacun sur une nouvelle ligne. Ils sont indentés par rapport à la procédure.

(procédure
 argument1
 argument2
 ...)

Enfin, on peut également placer le premier argument sur la même ligne que la procédure, et les arguments suivants sur des lignes séparées, au même niveau d’indentation que le premier.

(procédure argument1
           argument2
           ...)

Dans tous les cas, on retiendra que les arguments sont indentés au même niveau, et que l’on ne place jamais de parenthèses seules sur une ligne ! En effet, le lecteur humain comprend le code à sa forme, sans avoir jamais besoin de compter les parenthèses. Voici une manière de formater la solution de l’exercice plus haut qui mélange ces trois formes :

(-
 1
 (* 5 6)
 (* 3 (+ 4
         (* 3 12))))

Un programmeur Scheme voyant ce code remarque immédiatement que trois arguments sont passés à -, et voit où ils commencent et finissent, sans à examiner chacune des parenthèses individuellement.

Enfin, on peut retenir une règle supplémentaire, moins observée mais néanmoins utile : « closing paren closes the line ». Dès que l’on écrit une parenthèse fermante, il vaut mieux revenir à la ligne. Par exemple, plutôt que :

(+ (* 3 4) 5)

on écrira

(+ (* 3 4)
   5)

ou bien

(+
 (* 3 4)
 5)

ou encore, en échangeant les arguments pour mettre le plus complexe à la fin :

(+ 5 (* 3 4))

Suites d’expressions#

Dans un fichier LilyPond, le croisillon introduit une unique expression. S’il en faut plusieurs, par exemple pour définir des variables ou afficher des messages de débogage, on écrit un croisillon devant chacune.

#(display "Bonjour ")
#(define prénom "Josiane")
#(display prénom)

Une autre possibilité est d’enclore toutes les expressions dans un (begin ...).

#(begin
   (display "Bonjour ")
   (define prénom "Josiane")
   (display prénom))

Dans un begin peuvent apparaître un nombre quelconque d’expressions. Elles sont toutes évaluées, et la valeur de la dernière devient la valeur du begin entier.

(begin
  (display "Bonjour ")
  (define prénom "Josiane")
  (display prénom)
  (display " !\n")
  prénom)

 Bonjour Josiane !
 "Josiane"