INF 321
Les fonctions, passage par valeur/r´ ef´ erence, types produits
Eric Goubault
Cours 2
21 mai 2013
Dans le dernier ´ episode...
On a vu:
Le noyau imp´eratif
Quelques ´el´ements de s´emantique On va voir:
Quelques rappels sur la structure purement imp´erative des programmes Java
Les fonctions et les passages d’argument Les r´ef´erences (pointeurs)
Les types produits (d´ebut)
Structure simplifi´ ee des programmes (Java)
c l a s s Prog {
s t a t i c T1 x1 = t 1 ; . . . s t a t i c Tn xn = t n ; s t a t i c . . . f 1 ( . . . ) . . . s t a t i c . . . f p ( . . . ) . . .
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) { . . . } }
main
Le main est une fonction sp´eciale: c’est celle qui estle point d’entr´ee`a l’ex´ecution.
En argument, elle contient tous les arguments pass´es`a la ligne
Un peu de syntaxe (premi` ere approche)
Java
s t a t i c T f ( T1 x1 , . . . , Tn xn ) p
C
T f ( T1 x1 , . . . , Tn xn ) p
i n t f (c o n s t i n t x ,i n t y ) r e t u r n x +1;
Caml
l e t f x1 . . . xn = t i n p
l e t h y p o t h e n u s e x y = s q r t ( x∗. x +. y ∗. y )
Arguments toujours finaux
S´ emantique du main
Java
c l a s s E s s a i {
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) { f o r (i n t i =0; i<a r g s . l e n g t h ; i = i +1)
System . o u t . p r i n t l n ( ” a r g s [ ”+ i +”]= ”+a r g s [ i ] ) ; }
}
C
i n t main (i n t a r g c , c h a r ∗∗a r g v ) { i n t i ;
f o r ( i =0; i<a r g c ; i = i +1) p r i n t f ( ”a r g v [%d]=%s\n ” , i , a r g v [ i ] ) ; r e t u r n 0 ; }
Compilation, ex´ ecution
Java
> j a v a c e s s a i . j a v a
> j a v a E s s a i p r e m i e r a r g d e u x i e m e a r g a r g s [ 0 ] = p r e m i e r a r g
a r g s [ 1 ] = d e u x i e m e a r g
C
> g c c e s s a i . c−o e s s a i
> . / e s s a i p r e m i e r a r g d e u x i e m e a r g a r g [ 0 ] = e s s a i
a r g [ 1 ] = p r e m i e r a r g a r g [ 2 ] = d e u x i e m e a r g
Remarques...
M´ecanismes d’exceptions
En cas d’erreur ´eventuelle de traitement, permet d’interrompre un programme proprement enlan¸cant uneexception
Qui peut ˆetre r´ecup´er´ee par la fonction appelante pour traitement alternatif
Cf. Cours 4
Tous ces mots magiques...
static: quand on verra lesclasses, cours 4... En gros: il n’y a qu’un “´el´ement” de “type”Prog
public: permet d’exporterla d´efinition d’une fonction (pour ˆ
etre connue de l’ext´erieur): lemain doit forc´ement ˆetre
Structure des programmes (C)
T1 x1=t 1 ; . . .
Tn xn=t n ;
. . . f 1 ( . . . ) . . . . . .
. . . f p ( . . . ) . . .
i n t main (i n t a r g c , c h a r ∗∗a r g v ) { . . .
}
Structure des programmes (Caml)
Simplifi´e...
(∗ v a r i a b l e s g l o b a l e s ∗) l e t x = . . . ; ;
(∗ v a r i a b l e s l o c a l e s ∗) l e t x = . . . i n . . . ; ;
(∗ f o n c t i o n s − non r e c u r s i v e s ∗)
l e t f a r g 1 . . . a r g n = c o r p s de f o n c t i o n ; ;
Les fonctions en Java
Comment sont pass´es les arguments?
Exemple
c l a s s t r o i s V e r r e s {
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) { i n t a =1;
i n t b =2;
i n t c=a ; a=b ; b=c ;
System . o u t . p r i n t l n ( ”a=”+a ) ; System . o u t . p r i n t l n ( ”b=”+b ) ; }
}
java troisVerresdonnea=2,b=1, OK!
Codage par appel de fonction
c l a s s t r o i s V e r r e s {
s t a t i c v o i d swap (i n t x , i n t y ) { i n t z=x ; x=y ; y=z ; }
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) { i n t a =1;
i n t b =2;
swap ( a , b ) ;
System . o u t . p r i n t l n ( ”a=”+a ) ; System . o u t . p r i n t l n ( ”b=”+b ) ; } }
java troisVerres donnea=1,b=2, pas OK!
En Caml
l e t swap x y =
l e t z = r e f 0 i n ( z : = ! x ; x : = ! y ; y : = ! z ) a : = 1 ;
b : = 2 ; swap a b ; p r i n t i n t ! a ; p r i n t n e w l i n e ( ) ; p r i n t i n t ! b ; p r i n t n e w l i n e ( ) ;
Ne marche pas mieux...
R´ eponse
Passage par valeur
En Java, C, Caml, on n’a par d´efaut que lepassage par valeur (des variables scalaires)
La fonction travaille dans ce cas sur une copie des variables pass´ees en param`etre
call swap x ←amain y ←bmain
swap x,y
return;
copie args
swap local
Variables locales, variables globales
Variable globale
c l a s s C { s t a t i c i n t x ; x =3; x =0;
xvaut 0 `a la fin de l’ex´ecution Variable locale
s t a t i c v o i d r e s e t ( ) { i n t x =0; }
p u b l i c s t a t i c v o i d main ( S t r i n g a r g s [ ] ) { i n t x ; x =3; r e s e t ( ) ; . . .
xrecouvre deux variables en fait:
une variable locale `a main, qui vaut 3 et n’est pas modifi´ee une variable locale `a la fonction reset, qui vaut 0, et qui n’est connue que dans le corps de la fonction reset
cette variable locale cachela variable xdemain dans le corps de la fonctionreset, mais ne l’´ecrase pas (location distincte)
Illustration
. . . main . . .
i n t x ; x =3; xmain : 3
Illustration (2)
. . . r e s e t . . . i n t x ; x =0;
xmain : 3
xreset : 0
Illustration (3)
f i n du main
xmain : 3
xreset : 0
Cas du passage de tableaux en param` etre
Exemple
s t a t i c v o i d t a b p l u s u n (i n t[ ] y ){
i n t n = y . l e n g t h ;
f o r (i n t i = 0 ; i < n ; i = i +1) y [ i ] = y [ i ] + 1 ; }
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) { i n t[ ] x = new i n t[ 5 ] ;
f o r (i n t i = 0 ; i < 5 ; i = i +1) x [ i ] = i ;
t a b p l u s u n ( x ) ;
f o r (i n t i = 0 ; i < 5 ; i = i +1)
System . o u t . p r i n t l n ( ”x [ ”+ i +”]= ”+x [ i ] ) ; } Donne:
x [ 0 ] = 1 x [ 1 ] = 2 x [ 2 ] = 3 x [ 3 ] = 4 x [ 4 ] = 5
Pourquoi?
Passage de tableaux en param` etre
C’est un passage par r´ef´erence
Un tableau n’est pas un type simple (scalaire) mais compos´e:
trop lourd de faire des recopies `a l’appel de fonction
On ne passe que la r´ef´erence(=pointeur, adresse m´emoire...) au tableau, `a l’appel de la fonction
Cela permet de faire des modifications en place
Exemple
f o r ( i =0; i<5; i = i +1) x [ i ] = i ;
x
0 |1|. . . |4
Exemple
t a b p l u s u n ( x ) . . .
x y
0|1 |. . . |4
Dit autrement
ρ∈Env⊥ au moment de l’appel est:
ρ(x) =adru,ρ(y) =adru ρ(adru+i) =i pouri = 0, . . . ,4
Exemple
. . .
y [ i ] = y [ i ] + 1 ;
x y
1|2 |. . . |5
Par la s´emantique du cours 1
ρ0= [[y[i] =y[i] + 1]]ρ=ρ[[[y[i] + 1]]ρ/(ρ(y) +i)]
Et [[y[i] + 1]]ρ=ρ(ρ(y) +i) + 1 =ρ(adrt+i) + 1 =i+ 1 D’o`u `a la fin, le seul changement entreρ etρ0 est
ρ0(adrt+i) =i+ 1
Exemple
. . .
System . o u t . p r i n t l n . . .
x
1 |2 |. . .|5
Par la s´emantique du cours 1 [[x[i]]]ρ0 =ρ0(ρ0(x) +i)
Donc, [[x[i]]]ρ0 =ρ0(adrt+i) =i+ 1!
Rappel
c l a s s t r o i s V e r r e s {
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) { i n t a =1;
i n t b =2;
i n t c=a ; a=b ; b=c ;
System . o u t . p r i n t l n ( ”a=”+a ) ; System . o u t . p r i n t l n ( ”b=”+b ) ; }
}
java troisVerresdonnea=2,b=1, OK!
Rappel: autre codage...
c l a s s t r o i s V e r r e s {
s t a t i c v o i d swap (i n t x , i n t y ) { i n t z=x ; x=y ; y=z ; }
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) { i n t a =1;
i n t b =2;
swap ( a , b ) ;
System . o u t . p r i n t l n ( ”a=”+a ) ; System . o u t . p r i n t l n ( ”b=”+b ) ; } }
java troisVerres donnea=1,b=2, pas OK!
Passage par valeur / r´ ef´ erence
Ce que l’on voudrait, c’est passer lar´ef´erence aux variables a et bplutˆot que simplement leurs valeurs; pour pouvoir modifier les valeursen place
En Pascal c’´etait possible:
p r o c e d u r e t o t o (v a r i n t e g e r i , i n t e g e r j ) ; b e g i n
. . . end
(iest pass´ee par r´ef´erence,jest pass´ee par valeur)
En Java, C, Caml, il va nous falloir simuler ce passage, par le passage par valeur
Trouver la location d’une variable ` a partir de son nom (C)
Pour expliquer le m´ecanisme sous-jacent de pointeur...
i n t x , y ; i n t ∗p t r x ; x = 1 ;
ptrx
x : 1
Manipuler les valeurs et les adresses
i n t x , y ; i n t ∗p t r x ; x = 1 ; y = 0 ; p t r x = &x ;
ptrxcontient l’adresse en m´emoire o`u est stock´ee la valeur de la variablex.
ptrx
x : 1 y : 0
Trouver le contenu (valeur) de x ` a partir de sa location (C)
i n t x , y ; i n t ∗p t r x ; x = 1 ; y = 0 ; p t r x = &x ; y=∗p t r x ;
p r i n t f ( ”y=%d\n ” , y ) ;
Donney=1.
S´ emantique de * et &
∗
En supposant que x est en quelque sorte “bien typ´e” dans l’environnement ρ: ρ(x)∈Loc
Alors dans une expression (AExpr):
[[∗x]]ρ=ρ([[x]]ρ)
(se g´en´eralise `ax expressions de certains types...comme &y en particulier)
Dans une affectation:
[[∗x=e]]ρ=ρ[[[e]]ρ/ρ(x)]
(notez le niveau d’indirection, encore)
S´ emantique de * et &
&
Pour x∈Var, etρ: (Var∪Loc)→(Val∪Var∪Loc)!
On pose dans une expression (AExpr):
[[&x]]ρ=x∈Var
On ne peut utiliser &x `a gauche d’une affectation...
Exemple
[[∗(&x)]]ρ = ρ([[&x]]ρ)
= ρ(x)
S´ emantique de * et &
&
Pour x∈Var, etρ: (Var∪Loc)→(Val∪Var∪Loc)!
On pose dans une expression (AExpr):
[[&x]]ρ=x∈Var
On ne peut utiliser &x `a gauche d’une affectation...
Exemple
[[∗(&x)]]ρ = ρ([[&x]]ρ)
= ρ(x)
Manipuler les valeurs et les adresses
Si on avait plutˆot fait:
i n t x , y ; i n t ∗p t r x ; x = 1 ; y = 0 ; p t r x = &x ; x = 2 ;
ptrx
x : 2 y : 0
Manipuler les valeurs et les adresses
Si on avait plutˆot fait:
i n t x , y ; i n t ∗p t r x ; x = 1 ; y = 0 ; p t r x = &x ; x = 2 ; y =∗p t r x ;
p r i n t f ( ”y=%d\n ” , y ) ;
Donney=2
ptrx
x : 2 y : 2
En Caml
*homologue du ! de Caml
*t=u homologue det:=u de Caml
En Java
un objet est en fait un pointeur (vers la location m´emoire contenant toutes les informations sur l’objet)
un scalaire est une valeur, pas un pointeur vers une valeur
c l a s s t r o i s V e r r e s { p u b l i c s t a t i c v o i d main
( S t r i n g [ ] a r g s ) { I n t v a l a=new I n t v a l ( ) ; a . v a l =1;
I n t v a l b=new I n t v a l ( ) ; b . v a l =2;
swap ( a , b ) ;
S y s t e m . o u t . p r i n t l n ( ”a=”+a . v a l ) ; S y s t e m . o u t . p r i n t l n ( ”b=”+b . v a l ) ; }
c l a s s I n t v a l { p u b l i c i n t v a l ; . . .
Static ou pas?
c l a s s I n t v a l { p u b l i c i n t v a l ;
. . .
c l a s s t r o i s V e r r e s { . . .
a = new I n t v a l ( ) ; . . .
int val;dans class Intvaln’est pas static!
→ plusieurs “´el´ements” de “type”Intval peuvent ˆetre d´efinis, avec des valeurs enti`eres valdiff´erentes
new(comme sur les tableaux) permet d’allouer un nouvel
Constructeurs
Quand on fait a = new Intval();: cr´ee la case m´emoire associ´ee `a a(pouvant contenirval, entier) - fait un appel `a une fonction cach´ee: le constructeur par d´efaut Intval() D´eclaration
I n t v a l a ; a : ?
Constructeurs
Quand on fait a = new Intval();: cr´ee la case m´emoire associ´ee `a a(pouvant contenirval, entier) - fait un appel `a une fonction cach´ee: le constructeur par d´efaut Intval() Allocation
I n t v a l a ;
a = new I n t v a l ( ) ;
a
val : ?
Constructeurs
Quand on fait a = new Intval();: cr´ee la case m´emoire associ´ee `a a(pouvant contenirval, entier) - fait un appel `a une fonction cach´ee: le constructeur par d´efaut Intval() Affectation
I n t v a l a ;
a = new I n t v a l ( ) ; a . v a l = 1 ;
a
val : 1
Allocation/affectation en mˆ eme temps
Raccourci pour allocation et affectation, par nouveau constructeur
a =new I n t v a l ( ) ; a . v a l =1;
b =new I n t v a l ( ) ; b . v a l =2;
−→
p u b l i c I n t v a l (i n t u ) { v a l=u ; } a = new I n t v a l ( 1 ) ;
b =new I n t v a l ( 2 ) ;
Cette nouvelle fonction (constructeur) doit s’appeler du mˆeme nom que le programme et ne pas avoir de type; on l’appelle par new ...
Il existe toujours un constructeurpar d´efaut sans argument (pas besoin de le d´efinir)
Par contre...
Dans ce cas...
p u b l i c I n t v a l (i n t u ) { v a l=u ; } . . .
a = new I n t v a l ( 1 ) ; b =new I n t v a l ( 2 ) ;
On n’a plus acc`es au constructeur par d´efaut Intval()
Il faut red´efinir alors le constructeur par d´efaut:
p u b l i c I n t v a l ( ) { }
p u b l i c I n t v a l (i n t u ) { v a l=u ; }
On peut ainsi d´efinir un nombre quelconque de constructeurs (plus g´en´eralement, polymorphisme, cf. cours 4)
Comment coder l’´ echange?
Bonne version
s t a t i c v o i d g o o d s w a p ( I n t v a l u , I n t v a l v ) { i n t w = u . v a l ;
u . v a l = v . v a l ; v . v a l = w ; }
Mauvaise version
s t a t i c v o i d w r o n g s w a p ( I n t v a l u , I n t v a l v ) { I n t v a l w = u ;
u = v ; v = w ; } }
Ex´ ecution de wrong_swap
a = new I n t v a l ( ) ; a . v a l =1;
b = new I n t v a l ( ) ; b . v a l =2;
a b
val : 1 val : 2
Ex´ ecution de wrong_swap (1)
a = new I n t v a l ( ) ; a . v a l =1;
b = new I n t v a l ( ) ; b . v a l =2;
w r o n g s w a p ( a , b ) ;
a u b v
val : 1 val : 2
Ex´ ecution de wrong_swap (2)
I n t v a l w = u ;
a u w b v
val : 1 val : 2
Ex´ ecution de wrong_swap (3)
u = v ;
a w b v u
val : 1 val : 2
Ex´ ecution de wrong_swap (4)
v = w ;
a v w b u
val : 1 val : 2
Probl`eme!!
Ex´ ecution de good_swap
a = new I n t v a l ( ) ; // c o n s t r u c t e u r p a r d e f a u t a . v a l =1;
b = new I n t v a l ( ) ; b . v a l =2;
a b
val : 1 val : 2
Ex´ ecution de good_swap (1)
a = new I n t v a l ( ) ; // c o n s t r u c t e u r p a r d e f a u t a . v a l =1;
b = new I n t v a l ( ) ; b . v a l =2;
g o o d s w a p ( a , b ) ;
a u b v
val : 1 val : 2
Ex´ ecution de good_swap (2)
i n t w = u . v a l ;
a u b v
val : 1 val : 2 w : 1
Ex´ ecution de good_swap (3)
u . v a l = v . v a l ;
a u b v
val : 2 val : 2 w : 1
Ex´ ecution de good_swap (4)
v . v a l = w ;
a u b v
val : 2 val : 1 w : 1
Parfait!!
Remarque: les classes enveloppantes
Des programmes sont d´ej`a d´efinis en Java, permettant de manipuler des r´ef´erences sur des scalaires (commeIntval que l’on a d´efini plus haut)
Par exemple Integer
Pas utilisable ici carInteger est non-mutable!
Compliquons le jeu: enregistrements
Besoin de structurer les programmes, mais aussi les donn´ees!
Exemple: les coordonn´ees cart´esiennes
Soit p un point de l’espace N3. On peut le repr´esenter par le triplet de ses coordonn´ees (x,y,z),x,y etz ´etant un entier.
En vocable informatique: cas particulier de structureou enregistrement, avec 3champs entiersx,y, etz
Syntaxiquement (g´ en´ eralisation cours3)
Java
c l a s s P o i n t { i n t x ; i n t y ; i n t z ; }
C
s t r u c t P o i n t { i n t x ; i n t y ; i n t z ; };
Caml
t y p e P o i n t ={ m u t a b l e x : i n t ; m u t a b l e y : i n t ; m u t a b l e z : i n t ; }
Cas particulier de type produit!
N3 =N×N×N
→projections sur chaque coordonn´ee
→de quoi construire une variable `a partir des 3 coordonn´ees.
Java
P o i n t p=new P o i n t ( ) ; p . x = 1 ;
p . y = 2 ; p . z = 3 ;
i n t x x = p . x ; i n t y y = p . y ; i n t z z = p . z ; (projections)
Un peu de p´ edantique
X
X×Y
Y
Z πX
∀f
πY
∀g
∃!h
Propri´et´e universelle: ∀f,g,∃!h, tel que πX ◦h = f
πY ◦h = g
Dans les ensembles
X ×Y ={(x,y)|x ∈X,y ∈Y} πX(x,y) =x,πY(x,y) =y (s´electeurs)
h(z) = (f(z),g(z)) (constructeur)
(en fait Caml=”Categorical Abstract Machine Language”)
En C et Caml
C
s t r u c t p o i n t p ={ 0 , 2 , 3 };
p . x = 1 ;
(construction)
i n t x x = p . x ; i n t y y = p . y ; i n t z z = p . z ;
(projections) Caml
l e t p = r e f { x = 0 ; y = 2 ; z = 2 ; }; ;
! p . x<− 1 ; ;
(construction)
Constructeurs
Java
c l a s s P o i n t {
i n t x ; i n t y ; i n t z ;
P o i n t (i n t a , i n t b , i n t c ) { x = a ; // ou t h i s . x = a ; y = b ; // ou t h i s . y = b ; z = c ; // ou t h i s . z = c ; } }
Utilisation du constructeur
P o i n t x =new P o i n t ( 1 , 2 , 3 ) ;
Alloue, et fait l’affectation C, Caml
C: pas vraiment d’analogue direct; Caml: pas denew
Diff´ erences Java, C, Caml
Java
constructeurs, valeur vide (null) et valeurs par d´efaut C
pas de constructeur, valeur vide (null) et pas toujours de valeurs par d´efaut
Caml
pas de d´efinition de constructeur, pas denull, pas de valeur par d´efaut
Enregistrements et appels de fonctions
Java
c l a s s P o i n t { i n t x ; i n t y ; i n t z ; P o i n t (i n t u , i n t v , i n t w) {
x = u ; y = v ; z = w ; } } c l a s s E s s a i {
s t a t i c v o i d show ( P o i n t p ) { S y s t e m . o u t . p r i n t l n ( p+”=( ”+p . x+” , ”
+p . y+” , ”+p . z+”) ” ) ; }
s t a t i c v o i d i n c r ( P o i n t p , P o i n t q ) { q . x += p . x ; q . y += p . y ; q . z += p . z ; }
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) { P o i n t x0=new P o i n t ( 1 , 2 , 3 ) ;
P o i n t x1=new P o i n t (−1 ,−2 ,−3);
i n c r ( x0 , x1 ) ;
show ( x0 ) ; show ( x1 ) ; } }
Ex´ecution
> j a v a P o i n t
P o i n t @ 7 0 3 2 9 f 3 d = ( 1 . 0 , 2 . 0 , 3 . 0 ) P o i n t @ 5 9 b c a 5 f 1 = ( 0 . 0 , 0 . 0 , 0 . 0 )
Passage par r´ef´erence! (=valeur du pointeur sur objet de type Point)
Le GC
Garbage collector/Glaneur de cellules
Invent´e par John MacCarthy pour le LISP (prix Turing 1971) Permet de r´ecup´erer la m´emoire non utilis´ee, c’est un
processus qui s’ex´ecute en parall`ele et automatiquement:
d´etermine quels objets ne peuvent plus ˆetre utilis´es par un programme
r´ecup`ere cet espace m´emoire (pour ˆetre utilis´e lors d’allocations futures)
Nombreux algorithmes...impl´ement´es par exemple dans Java, Caml, mais pas dans C
Principes de fonctionnement (1)
Algorithmes “Mark and Sweep”
Le GC commence `a parcourir les locations m´emoiresvivantes (accessibles depuis les racines, i.e. les noms de variables du programme Java) - l’ex´ecution du programme Java est suspendue; 2 phases alors:
(mark): Les objets allou´es et visitables par le GC depuis les racines sonttaggu´es: visit´e et pas visit´e
(sweep): Le GC parcourt adresse par adresse letas(l’endroit en m´emoire o`u sont allou´es les objets) et “efface” les objets non taggu´es “visit´e”
Principes de fonctionnement (2)
Compteurs de r´ef´erences
Le GC maintient avec chaque objet, un nombre de r´ef´erences pointant sur chaque objet
Si ce compteur arrive `a z´ero, l’objet est lib´er´e...
Egalement...
“Stop and copy”
GC conservatifs, incr´ementaux, g´en´erationnels (cas de Java) etc.
Et dans les langages sans GC? (C...)
Allocation/d´eallocation manuelle Exemple (listes):
L i s t c o n s (i n t c a r , L i s t c d r ) { /∗ a l l o c a t i o n ∗/
L i s t r e s = ( L i s t ) m a l l o c (s i z e o f(s t r u c t s t L i s t ) ) ; r e s−>hd = c a r ; r e s−>t l = c d r ; r e t u r n r e s ; } v o i d f r e e l i s t ( L i s t l ) {
i f ( l == n u l l ) r e t u r n; f r e e l i s t ( l−>t l ) ; /∗ d e a l l o c a t i o n ∗/
f r e e ( l ) ; }
Cons´equences
C’est au programmeur de pr´evoir quand utiliserfree, freelistetc.
Parfois tr`es compliqu´e quand on fait du partage!
La prochaine fois
Enregistrements et appels de fonctions Types somme
Types r´ecursifs Bon TD!
La prochaine fois
Enregistrements et appels de fonctions Types somme
Types r´ecursifs Bon TD!