En ML, tout est des expressions. (En Pascal, expressions et instructions sont différentes.) Toute expression ML doit retourner une valeur (d'un certain type). Par exemple,
e_1 + e_2(
e_1 et e_2 sont de type entier)e_1 et e_2. Le résultat est v_1+v_2.
Idem pour
e_1 - e_2 e_1 * e_2 e_1 / e_2 e_1 mod e_2Sur les flottants, on a aussi
e_1 -. e_2 e_1 *. e_2 e_1 /. e_2et sur les booléens
e_1 || e_2 e_1 && e_2 not e_1Les comparaisons se font par
e_1 = e_2 e_1 <> e_2 e_1 < e_2 e_1 <= e_2 ...etc. (Pour le moment, ne pas utiliser
== ou !=). En
général, on peut avoir des opérateurs binaires op avec des
arguments d'un certain type et on écrit
e_1 op e_2(expressions nary-expressions dans la syntaxe du poly p.299 où
op est ``infix-op'')
Comme raccourci, on peut donner un nom à une valeur
avec la construction let ... in.
let x_1 = e_1 and .... x_n = e_n in eML calcule les valeurs v_1 de
e_1, ...
v_n de e_n;x_1=v_1, ... x_n=v_n; e.Exemples:
let x = 4 in x*(1-x)vaut -12
(let x = 4 in
x*(1-x))
+
(let x = 20 in
x*(x-1))
vaut 368
Dans ces 2 exemples, x est une variable locale dont le nom a peu
d'importance (variable muette ou alpha-conversion). On
pourrait aussi bien écrire
let y = 4 in y*(1-y)ou
(let z = 4 in
z*(1-z))
+
(let t = 20 in
t*(t-1))
Les let définissent des portées pour les identificateurs.
let x = 3 in
let y = 4 in
(2*x + 3*y)
a comme valeur 18 (de type int). On retrouve donc ttes les
subtilités des blocs et des variables locales.
Exemples:
let x = 3 in
let y = 4 in
let x = 1 in
(2*x + 3*y)
donne 14 (de type int).
Mais:
let x = 3 in
let y = 4 in
x + let x = 1 in
(2*x + 3*y)
donne 17(de type int), ainsi que :
let x = 3 in
let y = 4 in
(let x = 1 in
(2*x + 3*y))
+ x
qui donne aussi 17 (de type int)
let x = 3 in
let y = 4 in
(let z = 1 in
(2*z + 3*y))
+ x
let se calcule elle aussi, dans un certain
environnement. Dans
let x = e_1 in ele calcul de
e_1 se fait sans utiliser la nouvelle définition de
x. Plus intéressant, dans
let x = e_1 and y = e_2 in ele calcul de
e_2 utilise la valeur de x dans
l'environnement extérieur. Si on veut utiliser, la nouvelle valeur de
x pour calculer y, il faut écrire
let x = e_1 in
let y = e_2 in
e
Exercices: trouver les valeurs de
let x = 1 and y = 4 in let y = x in let z = y in y + z let x = 1 and y = 4 in let y = x and z = y in y + z
let définit la valeur d'une constante. Si une variable
veut avoir plusieurs valeurs différentes dans le temps, on devra
passer par une référence ou un tableau ou tout autre dénomination
de la mémoire.
Un programme est une suite de phrases (expressions ou
définitions de variables), ttes finies par le symbole ;;. Le
système répond en donnant la valeur et le type de la phrase.
let x = 1 ;; let y = 2 ;; x+y ;;auquel le système répond en indiquant la variable déclaré, son type et sa valeur.
x : int = 1 y : int = 2 - : int = 3Attention, au niveau programme, la construction
let correspond
à la définition de nouvelles variables jusqu'à la fin du programme.
Un programme qui fait de l'impression (formattée) doit tjrs commencer
par la directive (attention au #):
#open "printf" ;;
Comment faire l'instruction x := x + y + 3 de Pascal? Cela
consiste à changer la valeur de x. Mais, en Caml, tte variable
a une valeur constante!
Cela se réalise par une indirection à travers une case
mémoire, et on utilise une référence vers cette case.
ref eretourne la référence
ref v où v est la valeur de
e. (On peut voir une référence comme un pointeur vers une case
mémoire contenant v). De même,
!xsuppose que la valeur de
x est ref v, et retourne v comme
résultat. (On parle du contenu de la référence).
Si e est de type A, alors ref e est de type A
ref. (La notation est souvent suffixe sur les types).
L'expression
x := eretourne
() (la constante vide), de type unit, mais
modifie le contenu de x.
Exemple:
let y = 20 in let x = ref 1 in begin x := !x + y + 3; !x enddonne 24. Pourquoi?
(e_1; e_2; ... e_n) begin e_1; e_2; ... e_n endCalcule la valeur de
e_1 et l'oublie!
Calcule la valeur de e_2 et l'oublie!
...
Calcule la valeur de e_n qui est le résultat global.e_1, ... e_{n-1}).
if e then e_1 else e_2calcule
e (de type booléen). Si vrai, le résultat est
e_1. Sinon e_2. C'est une expression (comme les autres).e_1 et e_2 sont de même type. (Pour l'instant,
le conditionnel partiel (sans else) n'est pas autorisé.
match e with
pat_1 -> e_1
...
| pat_n -> e_n
calcule e et dispatche selon le cas. Mêmes remarques que pour
if.
try e with
pat_1 -> e_1
...
| pat_n -> e_n
calcule e qui est le résultat, sauf si exception que l'on teste
derrière le with. On donne alors e_1 ou ... e_n
comme résultat.
Pour itérer, on utise
while e_1 do e_1; ... e_n done for i = e_1 to e_2 do e_1; ... e_n donequi renvoient la valeur vide après le bon nombre d'itérations.
raise Exit failwith sn'ont pas de valeur, mais lève une exception
Exit (resp
Failure avec la chaine s comme argument)
Pour sortir brutalement d'une boucle while, on peut utiliser
try
while true do
...
raise Exit
...
done
with
Exit -> e_1
Entiers int: 1 2 3 ... -1 -2
Booléens bool: true, false
Réels float: 2.3 3E4 -2E-9
Caractères char: `a`, `b`, etc. , `\n`,
`\r`, etc
Paires (nuplets): int * int: (1, 2)
Pour accéder aux éléments d'un paire, on se sert du filtrage.
let (x, b) = (1, true) in
if b then x
else 0
ou encore
match (1, true) with
(x, b) -> if b then x
else 0
Les filtres sont appelés pattern en anglais (et dans la syntaxe
du polycopié). Le filtre universel _ filtre tout. Il sert
notamment dans le dernier cas par défaut d'un match.
Tableaux int vect: [| 1; 2; 3 |]
L'expression
a.(i)retourne la valeur de l'élément
i du tableau aa.(i) <- eretourne la valeur vide en mettant dans l'élément
i du tableau
a la valeur de e
vect_length adonne la taille du tableau
a
make_vect n eretourne un tableau de taille
n initialisé par e
Exemple:
let a = make_vect 10 0 in
for i=0 to vect_length a do
a.(i) <- i*i
done
Les définitions
let f (x) = x + 1 ;; let g (x, y) = x + y ;; let h (x) (y) = x + y ;;produisent comme résultat:
f : int -> int = <fun> g : int * int -> int = <fun> h : int -> int -> int = <fun>La valeur d'une fonction n'est pas imprimée, mais son type est indiqué. Les constantes de fonction permettent de définir des fonctions anonymes.
function (x) -> x+1qui a comme type
int -> int. Les définitions précédentes
sont équialentes à:
let f = function (x) -> x + 1 ;; let g = function (x, y) -> x + y ;; let h = function (x) -> function (y) -> x + y ;;
let f x = x ;; let g (x, y) = x ;; let nth (a, n) = a.(n) ;;donnent
f : 'a -> 'a = <fun> g : 'a * 'b -> 'a = <fun> nth : 'a vect * int -> 'a = <fun>
'a (lire alpha) est une variable de type. On peut donc
utiliser f dans tout contexte où alpha peut être intancié.
Par exemple,
f 2 ;; - : int = 2ou
f "aaaa" ;; - : string = "aaaa"
Pb: quel est le type de := ??
prefix := ;;donne la réponse...
#open "printf";; let initialisation a = for i = 0 to vect_length a - 1 do a.(i) <- random__int 100 done;; let impression a = for i = 0 to vect_length a - 1 do printf "%d " a.(i) done; printf "\n" ;; let main () = let a = make_vect 15 0 in begin initialisation (a); (* On lit le tableau *) tri_selection (a); (* On trie *) impression (a); (* On imprime le resultat *) end ;;On utilise des fonctions de librairie pour tirage au sort (entre 1 et 100) ou impression formattée.
(* cf polycopié *)
let tri_selection a = let min = ref 0 in for i = 0 to vect_length a - 2 do min := i; for j = i + 1 to vect_length a - 1 do if a.(j) < a.(!min) then min := j done; let t = a.(!min) in a.(!min) <- a.(i); a.(i) <- t done;;(Remarque syntaxique:
if partiel marche ici car la seule
alternative min := j est de type unit, qui est aussi le
type par défaut du else)
On peut même faire un tri polymorphe (en passant la relation d'ordre en argument):
let tri_selection a plus_petit_que = let min = ref 0 in for i = 0 to vect_length a - 2 do min := i; for j = i + 1 to vect_length a - 1 do if plus_petit_que(a.(j), a.(!min)) then min := j done; let t = a.(!min) in a.(!min) <- a.(i); a.(i) <- t done;;
Nombre moyen et nombre pire de comparaisons?
(* cf polycopié *)
let tri_insertion a =
let j = ref 0 in
for i = 1 to vect_length a - 1 do
let v = ref a.(i) in
begin
j := i;
while !j > 0 && a.(!j - 1) > !v do
a.(!j) <- a.(!j - 1);
decr j
done;
a.(!j) <- !v;
end
done;;
où decr est définir pas
let decr (x) =
x := !x -1 ;;
(c'est en fait une fonction de bibliothèque). Itou pour incr.
Nombre moyen et nombre pire de comparaisons?
let initialisation (n) =
let a = make_vect n 0 in
begin
for i = 0 to vect_length a - 1 do
a.(i) <- random__int 100
done;
a
end
(* cf
polycopié *)
C'est une variante astucieuse du tri par insertion qui essaie
d'éliminer les longues séquences à parcourir.
let tri_shell a =
let h = ref 1 in
while !h <= vect_length a - 1 do
h := 3 * !h + 1
done;
while !h > 1 do
h := !h / 3;
for i = !h to vect_length a - 1 do
if a.(i) < a.(i - !h) then begin
let v = a.(i) in
let j = ref i in
while !j >= !h && a.(!j - !h) > v do
a.(!j) <- a.(!j - !h);
j := !j - !h
done;
a.(!j) <- v
end
done
done;;
Nombre moyen et nombre pire de comparaisons? C'est un pb ouvert!
Il y a bien d'autres tris (cf Knuth, vol 3; Searching and Sorting). Si on connait la distribution des valeurs et qu'elles ne soient pas très grandes, on peut arriver à trier en temps linéaire.
(cf. le TD1 de Bruno Salvy en
http://www.polytechnique.fr/poly/salvy/)
Tri distribution, tri des mécanographes (Straight Radix Sort). Enfin, si toutes les données ne tiennent pas dans la mémoire vive d'un ordinateur (c'est de moins en moins le cas), il y a des méthodes sophistiquées de tri externe.