Jusqu'à présent, nous avons vu les différentes constructions de
Scheme en donnant une sémantique (qu'est-ce que fait l'évaluateur?)
intuitive et en regardant (grâce au « pas à pas ») comment
l'évaluateur tavaillait. Mais cette façon de faire a ses limites
puisque nous ne pouvons pas utiliser le « pas à pas » de DrScheme
pour n'importe quel programme (pas de
let, pas de
définition interne...). Aussi, dans cette section, nous voudrions
donner une sémantique plus précise et plus générale.
Commençons par une expression simple, lorsque l'on peut utiliser le
« pas à pas »:
;;;
;;;
(define (a-disque r)
(* 3.1416 r r))
;;;
(a-disque (+ 20 2))
On peut visualiser le processus de calcul en utilisant DrScheme en
mode pas à pas.
Comment çà marche? On veut calculer:
(a-disque (+ 20 2))
Dans un premier temps, on doit calculer l'argument de la fonction:
(a-disque (+ 20 2) )
les arguments de la fonction
+ sont des valeurs et, cette
fonction étant une primitive, elle est calculée
« d'un coup »:
-->
(a-disque 22 )
et maintenant que fait-on?
La fonction
a-disque n'est pas une primitive, elle a été
définie par le programmeur: on prend sa définition, ou plutôt le
corps de cette définition, dans laquelle on « remplace » la variable
par la valeur de l'argument:
(a-disque 22)
-->
(* 3.1416 22 22)
|
(define (a-disque r)
(* 3.1416 r r))
|
encore une fois, pour cette application, la fonction est une primitive et
les arguments sont des valeurs: le calcul s'effectue immédiatement:
(* 3.1416 22 22)
-->
1520.5344
Terminologie: classiquement, en informatique, on ne dit pas
qu'on « remplace » la variable par la valeur de l'argument mais l'on
dit que l'on
substitue, dans le corps de la fonction, la
variable par l'argument. La sémantique de Scheme que nous vous
présentons est appellée
modèle par substitution.
Mais où trouve-t-on la définition de la fonction
a-disque?
Dans l'environnement dans lequel on évalue l'expression.
En informatique, cette notion d'environnement se décline à tous les niveaux:
-
Lorsque l'on travaille sur ordinateur (quoi qu'on fasse), on a besoin
d'un certain ensemble de ressources matérielles -- par exemple, si
l'on veut traiter des images, on a besoin d'un scanner et d'une
(bonne) imprimante couleur -- et logicielles -- par exemple, si vous
voulez faire des exercices sur votre ordinateur, vous avez besoin d'un
interprète Scheme. C'est l'environnement matériel et logiciel nécessaire.
- Le système d'exploitation qui vous fournit des outils comme la
possibilité d'imprimer et qui comporte des variables
d'environnement, dont la valeur peut varier selon l'utilisateur,
selon ce que fait l'utilisateur...
- À l'intérieur de ce système d'exploitation, on peut être dans
différents environnements (sous Linux, plusieurs langages de
commandes (Shell), plusieurs environnements graphiques...).
- Dans la plupart des logiciels, DrScheme en particulier, on peut
être dans différents environnements (niveau
d'apprentissage...).
- En Scheme, comme dans tous les langages de programmation,
l'évaluation d'une expression est effectuée dans un certain
environnement. Ainsi, lorsque l'on
définit une fonction, on ajoute cette définition à
l'environnement et, dans cet environnement, on peut utiliser cette
fonction.
Une autre façon de voir les choses est de dire que l'environnement
(quel que soit le niveau où l'on se place) est constitué de choses, de
trucs, qui sont indispensables pour effectuer une tâche et que l'on
n'a pas besoin de citer explicitement dans la description du
traitement à effectuer: il faut d'autant plus avoir conscience de
l'existence de cet environnement qu'il est absent du texte descriptif.
Ainsi, pour évaluer une expression, il faut aussi savoir dans quel
environnement on se place.
Au départ -- on dit au top-level --, l'environnement est constitué par
toutes les primitives (*, +... pair?, car...). On peut enrichir cet
environnement en définissant des fonctions, le nouvel environnement
comportant alors les primitives et les fonctions définies par le
programmeur.
Ainsi, dans l'exemple précédent, lorsque l'on évalue l'expression
(a-disque (+ 20 2)), l'environnement est constitué par
les primitives et la définition de la fonction
a-disque.
Premier exemple: on demande l'évaluation de:
;;;
;;;
(define (a-disque r)
(* 3.14 r r))
;;;
;;;
;;;
(define (v-cylindre r h)
(* (a-disque r) h))
;;;
(v-cylindre 22 3)
En indiquant dans la colonne de gauche l'environnement et dans celle
de droite la liste des substitutions, on obtient:
|
Environnement
|
|
Évaluation
|
|
(define (a-disque r)
(* 3.14 r r))
(define (v-cylindre r h)
(* (a-disque r) h))
|
|
(v-cylindre 22 3)
-->
(* (a-disque 22) 3)
-->
(* (* 3.14 22 22) 3)
-->
(* 1519.76 3)
-->
4559.28
|
Second exemple: lorsque nous avons étudié la récursivité sur
les listes, nous avons défini la fonction « concaténation droite »
nommée
conc-d:
;;;
;;;
(define (conc-d L x)
(if (pair? L)
(cons (car L) (conc-d (cdr L) x))
(list x)))
(conc-d '(a b) 'c)
Comment évalue-t-on l'expression (il s'agit d'une révision sur la
récursivité)? L'environnement ne comporte que les
primitives et la définition de la fonction
conc-d. L'évaluation
est alors:
(conc-d '(a b) 'c)
On substitue, dans la définition de
conc-d,
L par
'(a b) et
x par
'c:
-->
(if (pair? '(a b))
(cons (car '(a b)) (conc-d (cdr '(a b)) 'c))
(list 'c))
L'expression est une alternative. On doit donc évaluer la condition,
constituée par une application d'une primitive:
-->
(if #t
(cons (car '(a b)) (conc-d (cdr '(a b)) 'c))
(list 'c))
La condition étant vraie, pour évaluer l'alternative, on doit évaluer sa
conséquence:
-->
(cons (car '(a b)) (conc-d (cdr '(a b)) 'c))
On évalue alors les deux sous-expressions:
--> -->
(cons 'a (conc-d '(b) 'c))
-->
Il faut alors évaluer (conc-d '(b) 'c).
Pour ce faire, on substitue, dans la définition de conc-d, L par
'(b) et x par 'c:
(cons 'a
(if (pair? '(b))
(cons (car '(b)) (conc-d (cdr '(b)) 'c))
(list 'c))
)
L'alternative est évaluée comme précédemment en évaluant la condition:
-->
(cons 'a
(if #t
(cons (car '(b)) (conc-d (cdr '(b)) 'c))
(list 'c))
)
et comme cette dernière est vraie, on évalue la conséquence:
-->
(cons 'a (cons (car '(b)) (conc-d (cdr '(b)) 'c)))
On évalue les deux expressions (car '(b)) et (cdr '(b)):
--> -->
(cons 'a (cons 'b (conc-d '()'c) ))
Il faut alors évaluer (conc-d '() 'c).
Pour ce faire, comme d'habitude, on substitue, dans la définition de
conc-d, L par '() et x par 'c:
-->
(cons 'a
(cons 'b
(if (pair? '())
(cons (car '()) (conc-d (cdr '()) 'c))
(list 'c))
))
et on évalue la condition:
-->
(cons 'a
(cons 'b
(if #f
(cons (car '()) (conc-d (cdr '()) 'c))
(list 'c))
))
La condition étant fausse, on évalue l'alternant:
-->
(cons 'a (cons 'b (list 'c) ))
et, en deux pas (exécution de la primitive cons), on trouve:
--> -->
(a b c)