Module programmation: évaluation.
|
Plan des 4 prochains cours
-
Environnements
- Fonctionnalité
- Application de fonction
- Récursivité
- Traits impératifs
-
Mémoire, références
- Exceptions
- Correction de programmes
- 1
- Renaud.Rioboo@lip6.fr
Évaluation
Espace d'expressions |
Espace de valeurs |
1+2 |
3 |
if true then 1 else 2 |
1 |
let y = 1 in y+1 |
2 |
function x -> x + 1 |
«x:->x+1,E» |
let y = 1 in function x -> x + y |
«x:->x+y,E1» |
Une expression Caml-Light prend une valeur dans un environnement. C'est l'évaluation.
Un identificateur est un nom2.
Un environnement permet d'associer
une valeur à un identificateur.
Une liaison est l'association d'un identificateur à une valeur.
Dans un environnement il peut y avoir plusieurs liaisons pour un même
identificateur, c'est le masquage.
Si E est un environnement, E est une expression et V une valeur, on
notera E -E-> V si E s'évalue en V dans
l'environnement E.
Ainsi si E est l'environnement courant, on a :
-
true -E-> true,
- 1 -E-> 1,
- (if true then 1 else 2) -E-> 1
L'environnement global est l'environnement dans lequel le programme se
trouve.
- 2
- que l'on utilise souvent dans une
fonction ou pour pour éviter des re-calculs
Portée dynamique
-
En portée dynamique la valeur d'un identificateur se trouve dans
l'environnement global (interprétation, langages de shell)
let x = 1 |
E=ë(x=1)<|Eû |
let f = |
function y -> x + y |
E=ë(f=F)<|Eû |
f 1 |
2 |
let x = 2 |
E=ë(x=2)<|Eû |
f 1 |
3 |
La portée dynamique n'est pas compatible avec le typage statique.
Portée statique
-
En portée statique (ou lexicale) la valeur d'un identificateur se
trouve dans l'environnement de définition (compilation)
let x = 1 |
E1=ë(x=1)<|E0û |
let f = |
function y -> x + y |
E2=ë(f=F)<|E1û |
f 1 |
2 |
let x = `a` |
E3=ë(x=`a`)<|E2û |
f 1 |
2 |
Interaction avec le système de fichiers
Eg = Eu Å Em Å Eb
L'environnement global est la juxtaposition de
-
Eu, session utilisateur en cours,
- Em, les modules ouverts
- Eb, la base.
Une compilation (camlc -c) produit :
-
un fichier .zo de code et
- un fichier .zi d'interface obtenu à partir d'un fichier de
description d'interface .mli.
La directive #open mod permet de rendre visible les identificateurs décrits dans mod.zi sans avoir à les nommer par
mod__ident.
L'interprète possède
-
une fonction load (: string -> unit) qui
permet de charger un module source (.ml) en mémoire.
-
une fonction load_object (: string -> unit) qui
permet de charger un module source (.zo) en mémoire.
En mode compilé il faut inclure le fichier .zo dans la ligne de commande
camlc -o bar foo.zo bar.ml
pour obtenir les valeurs définies dans le module foo et
rendre visibles les identificateurs de foo en incluant la
directive
#open "foo"
dans le fichier bar.ml
Terminologie
MATHS |
INFORMATIQUE |
variable |
paramètre formel |
paramètre |
paramètre libre |
argument |
paramètre effectif |
fonction |
fonction partiellement définie |
application |
fonction |
calculer f(exp) |
appliquer f à exp |
f(exp) |
f exp |
On doit distinguer l'expression f(exp) de la valeur qu'elle prend dans
un environnement. On appelle application une expression de la forme
E1 E2.
Les fonctions
Les fonctions sont des valeurs Caml-Light que l'on nomme des fermetures.
En Caml-Light elles sont caractérisées par :
-
un nom de variable, c'est le paramètre formel (nom),
-
une expression définissant la valeur de la fonction en tout point que
l'on nomme le corps de la fonction (corps),
- un environnement de définition de la fonction (Env) :
On les note : «nom:->corps,Env»
On notera souvent
-
les expressions en lettres calligraphiques ( E) ou sous
forme de pseudo code (expr)
- les valeurs ou les environnements en notation mathématique
(V ou E)
Appel par Nom
let deux = (print_string "1+1" ; 1 + 1) in
deux + deux ;;
-
liaison de deux à
(print_string "1+1" ; 1 + 1)
puis évaluation de deux + deux
-
substitution de deux à sa ``valeur'' soit :
(print_string "1+1" ; 1 + 1) +
(print_string "1+1" ; 1 + 1)
- affichage de "1+1" calcul de 1 + 1 = 2
- affichage de "1+1" calcul de 1 + 1 = 2
- calcul de 2 + 2 = 4
Les identificateurs sont remplacés par des expressions qui sont réévaluées.
Appel par Valeur
let deux = (print_string "1+1" ; 1 + 1) in
deux + deux ;;
-
calcul de la valeur de
(print_string "1+1" ; 1 + 1)
- affichage de "1+1" retour de 2
-
liaison de deux à sa valeur 2
-
calcul de deux + deux soit 2+2
- retour de 4
Les identificateurs sont remplacés par des valeurs et ne sont pas réévalués.
La liaison
-
Définitive : on modifie l'environnement global
let x = 1 ;;
-
évaluation de 1.
- enrichissement de l'environnement global de la liaison de x à 1
E=ë(x=1)<|Eû
-
Temporaire : on créée un nouvel environnement pour évaluer une
expression (portée lexicale) :
let x = 1 in x+1;;
-
évaluation de 1.
- création d'un nouvel environnement contenant la liaison de x à 1
E1=ë(x=1)<|Eû
- évaluation de x+1 dans E1
L'environnement global n'est pas modifié !
Les fonctions comme valeurs
On peut étendre les opérations booléennes aux fonctions d'une
variable booléenne :
type fonctions_booleennes = Fbool of bool -> bool ;;
let vrai = Fbool (function x -> true)
and faux = Fbool (function x -> false);;
let ou = function f1 -> function f2 ->
match (f1,f2) with
((Fbool f1), (Fbool f2)) ->
Fbool (function x -> (f1 x) || (f2 x)) ;;
Les fonctions sont des valeurs spéciales
let applique = function f1 ->
match f1 with
Fbool f1 -> function x -> f1 x ;;
let est_vrai = ou vrai vrai ;;
applique est_vrai true = applique vrai true ;;
applique est_vrai false = applique vrai false;;
est_vrai = vrai;;
Liaison Caml-Light
La liaison permet de déstructurer des valeurs et d'introduire des identificateurs :
type point = {abs : int ; ord : int} ;;
let ({abs = x ; ord = y} as un_point) =
{abs = 10 ; ord = 20 } ;;
Introduit globalement trois nouveaux noms :
-
x : int = 10,
- y : int = 20 et
- un_point : point = {abs = 10 ; ord = 20}
il n'y a pas de calcul dans les parties gauches d'un let, ce sont des motifs lexicaux. Une telle liaison peut être mal typée ou échouer :
let (a,b) = (1,2,3) in a+b ;;
let (a,1) = (1,2) in a+1 ;;
let projection_verticale = function a_point ->
match a_point with
{abs = x ; ord = y} ->
if (y = 0) then a_point
else {abs = x ; ord = 0};;
Le motif ``_'' (souligné) permet de déstructurer sans nommer.
let abscisse = function un_point ->
match un_point
with {abs = x ; ord = _} -> x ;;
Application
Appliquer une fonction f à une valeur x c'est calculer la valeur de
f(x).
Il faut évaluer l'expression constituant le corps de la fonction
dans l'environnement de définition de la fonction augmenté de la liaison où
le paramètre formel est lié à la valeur d'appel (le paramètre effectif).
Soient Fe et Xe deux expressions Caml-Light, la
valeur de l'application Fe( Xe) dans l'environnement
E est la valeur de la simplification de F(xv) où F est la valeur de
Fe dans E et xv est la valeur de Xe dans E.
Si F=«x:-> Ex,Ed», alors F(xv) a la même valeur
que
celle de Ex dans l'environnement
ë(x=xv)<|Edû
On peut donc écrire une règle de simplification d'une application :
Fe -E-> («x:-> Ex,Ed») |
Xe -E-> xv |
Ex -ë(x=x)<|Edû-> V |
|
Fe( Xe) -E-> V |
pour obtenir la valeur d'une application il faut :
-
évaluer l'expression en position fonctionnelle,
- évaluer l'expression en position d'argument,
- augmenter l'environnement de définition de la fermeture de la liaison
entre le paramètre formel et le paramètre effectif pour évaluer le corps de la
fermeture.
Fonctions à plusieurs arguments
Soit E l'environnement courant, a-t-on deux choix ?
-
let foo = function (x,y) -> E(x,y)
L'expression n'a qu'un seul argument qui est le couple (x,y) et foo a pour type sx*sy ® s E
Sa valeur est une fermeture
F1=«x,y:-> E(x,y),E»
-
let foo =
function x -> function y -> E(x,y)
L'expression est une fonction curryfiée et foo a pour type
sx®sy ® s E.
Sa valeur est une fermeture
F2=«x:-> Ex,E»
où Ex est l'expression
function y -> E(x,y).
Application partielle
Soit Efoo l'environnement de définition de F2.
On peut appliquer F2 à une valeur x0 de type sx.
C'est une application partielle, le résultat en est une autre
fermeture :
Fx0= «y:-> E(x,y),ë(x=x0)<|Efooû»
qui a pour type sy ® sE
-
F1 et F2 n'ont pas le même type,
- On évalue F2 en liant x et y en même temps,
- F1 et F2 ne sont pas équivalentes.
Fonctions
La construction
fun x1 ...xn -> E
est équivalente à
function x1 -> ...function xn -> E
L'écriture let F x1 ...xn = E
est équivalente à
let F = fun x1 ...xn -> E
La forme où fun ou function est explicite permet de préciser son
environnement de définition :
let incremente_compteur = let compteur = ref(0) in
function n ->
begin
compteur := !compteur + n ;
!compteur
end ;;
Le Filtrage
avec la notation
|
| F -> E |
= |
ì í î |
| F1 -> E1 |
... |
| Fn -> En |
|
|
|
-
Les Fi sont des motifs lexicaux qui introduisent de
nouveau noms. Ils permettent de produire un environnement d'exécution de
la clause.
- Les Ei sont des expressions qui constituent le corps de
la clause, elles sont évaluées dans l'environnement de définition de la
fonction augmenté des liaisons produites pendant le filtrage.
-
Chaque clause de filtrage | Fi ® Ei
va :
-
permettre de sélectionner un sous-ensemble de valeurs du type
sv
que doit avoir V.
- permettre de produire un environnement dans lequel va s'évaluer
l'expression Ei qui définit le corps de la clause comme dans :
let Fi = V in Ei exécuté dans
un environnement E tel que ( V -E-> V).
On
notera Ei les liaisons produites pendant ce filtrage Ei Å E
l'environnement obtenu après avoir augmenté E de ces liaisons.
À l'appel, le paramètre effectif V qui est l'évaluation de l'expression
V dans l'environnement courant est ``superposé'' avec les
filtres Fi dans l'ordre du texte.
On peut donc énoncer une règle pour l'évaluation d'un filtre :
V -E-> V, |
$ i (| Fi ® Ei) Î
(| F ® E) |
et i est le premier indice qui |
produise des liaisons Ei de |
let Fi = V, |
Ei -(Ei Å E)-> R |
|
[match V with
(| F ® E)] -E-> R |
La construction
match V with | F ® E
est équivalente à (function | F ® E) V.
On peut ajouter des conditions a une clause de filtrage Fi qui
a alors la forme :
| Fiwhen Ci-> Ei
Où Ci est une expression qui doit être de type bool. Elle est
évaluée dans l'environnement d'exécution du corps de la clause :
Si V est le paramètre effectif, et si E est l'environnement courant,
Ei et Ci s'évaluent tous deux dans Ei Å E qui est
E augmenté des liaisons Ei produites pendant le filtrage.
cette construction est à éviter
La Récursivité
Dans le corps d'un let les identificateurs (noms de
fonctions) sont cherchés dans
l'environnement courant.
La construction
let rec
introduit un identificateur récursif.
Soit Ec
l'environnement courant, la déclaration
let rec foo = E
crée une valeur Vfoo qui est le
résultat de l'évaluation de l'expression E dans l'environnement
Efoo=ë(foo=Vfoo)<|Ecû où E -Efoo-> Vfoo
et Efoo devient l'environnement courant.
let rec fib = function n -> match n with
| 0 -> 0
| 1 -> 1
| _ -> fib(n-1) + fib(n-2) ;;
définit un environnement Efib, une fermeture
Ffib=«n:-> Efib,Efib»
l'évaluation d'une fonction récursive se fait comme une évaluation normale.
À l'appel d'une fonction récursive, il faut empiler les
environnements :
-
Soit Efib l'environnement de définition de fib et E
l'environnement courant.
-
Évaluation de fib(3) dans E :
Ffib(3) puisque fib -E-> Ffib et
3 -E-> 3. On évalue
E3 =fib(n-1) + fib(n-2)
dans E3 =ë(n=3)<|Efibû,
évaluation de fib(2) dans E3 en suspens et :
-
évaluation de fib(1) dans E3 soit l'évaluation de Ffib(1)
qui vaut 1. Donc fib(1) -E3-> 1
- reprise de l'évaluation de fib(2) dans E3 :
évaluation de Ffib(2) puisque
fib -E3-> Ffib et
2 -E3-> 2
On doit évaluer
E2 = fib(n-1) + fib(n-2)
dans E2 = ë(n=2)<|Efibû :
-
évaluation de fib(0) dans E2 soit 0
- évaluation de fib(1) dans E2 soit 1
-
Donc E2 -E2-> 1
- et enfin
E3 -E3-> 2
les types Somme
La déclaration
type foo = C | C1 of t1 | C2 of t2
introduit des
-
constructeurs de valeurs du type foo :
-
une constante
C : foo
- deux fonctions
-
C1 : t1 -> foo
- C2 : t2 -> foo
qui allouent de nouvelles valeurs du type foo
- motifs lexicaux de filtrage des valeurs de type foo :
-
un motif C qui filtre cette valeur,
- deux motifs C1 _ et C2 _ qui filtrent les autres.
qui n'allouent pas mais introduisent des noms
les Types Sommes
Sont récursifs :
type somme_formelle =
| Variable
| Entier of int
| Somme of somme_formelle * somme_formelle ;;
let rec eval = function (expr, valeur) ->
| (Variable , _ ) -> valeur
| (Entier n , _ ) -> n
| (Somme (e1,e2) , _ ) ->
eval(e1,valeur) + eval(e2,valeur) ;;
Ensuite :
eval(Somme(Variable,Entier 2) , 4) vaut 6.
Ordre supérieur
Sommer tous les éléments d'une liste ?
let rec somme = function l -> match l with
| [] -> 0
| h::r -> h + (somme r) ;;
Ici
0:int et (prefix +):int -> int -> int.
let rec somme_abs =
function zero -> function plus -> function l ->
match l with
| [] -> zero
| h::r ->
(plus h (somme_abs zero plus r)) ;;
On généralise
zero: a et plus: b->a->a.
let somme = function l -> somme_abs 0 (prefix +) l;;
Pour tout environnement E si F est un identificateur (monomorphe) tel
que
F -E-> F et si
(function x -> F(x)) -E-> G on a :
GºF dans le sens où
" x G(x) = F(x) et
function x -> F(x) Û F
let somme = somme_abs 0 (prefix +) ;;
let mult = somme_abs 1 (prefix *) ;;
Récursion terminale
L'enveloppe d'un appel récursif est l'ensemble des calculs qui doivent
d'effectuer après cet appel.
Lors d'un appel récursif, les différents environnements doivent être conservés
car d'autres calculs doivent s'exécuter après :
let rec foo = function n -> match n with
| 0 -> 2
| _ -> foo(n-1) + n;;
Soit E l'environnement courant, si
foo -E-> Ffoo,
Ffoo =«n:-> Efoo,Efoo»
Pour évaluer foo(1) dans E, on doit évaluer Ffoo(1) et donc
évaluer (foo(n-1) + n) dans E1 =ë(n=1)<|Efooû. Si foo(n-1) est évalué en premier il faut restaurer E1 pour
évaluer n.
Un appel récursif est terminal s'il n'y a pas d'autres calculs à
effectuer ensuite. Le résultat de la fonction est alors lui même un
appel récursif.
let rec foo_bis =
function n -> function res -> match n with
| 0 -> res
| _ -> foo_bis (n-1) (res + n);;
" xFfoo(x) = (Ffoo_bisx2)
Si (n -En+1-> n) et
(res -En+1-> r)
il est inutile de stocker En+1 pour déterminer la valeur de
foo_bis (n-1) (res + n) dans En+1
puisque ((n-1) -En+1-> n-1) et
((res + n) -En+1-> r+n) peuvent être calculés
avant
Récursion terminale
Lorsqu'un appel récursif est enveloppé par
une opération associative on peut rajouter un paramètre pour
obtenir un appel terminal.
let rec foo = function arg ->
if (arg = arg_0)
then val_0
else op(foo(g(arg)), f(arg));;
où f et g ne contiennent pas foo, devient lorsque op
est associative :
let rec foo = function (arg,accu)
if (arg = arg_0)
then op(accu,val_0)
else foo(g(arg) , op(accu, f(arg)));;
Dichotomie
Calcul de an = |
(an/2)2 |
si n est pair |
a(an/2)2 |
si n est impair |
|
let rec pow = function a -> function n ->
if ((n mod 2) = 0)
then
if (n = 0)
then 1
else
let res = pow a (n / 2) in res * res
else
if (n = 1)
then a
else
let res = pow a (n / 2) in a * res * res ;;
On peut généraliser 0, 1 et * :
let rec dich = fun zero un op a n ->
if ((n mod 2) = 0) then
if (n = 0)
then zero
else
let res = dich zero un op a (n/2) in
op res res
else
if (n = 1)
then un
else
let res = dich zero un op a (n/2) in
op a (op res res) ;;
Évaluation des arguments
Pour des raisons d'efficacité
Caml-Light évalue les arguments d'une application du dernier au premier :
let foo = function x -> function y -> !x + !y ;; |
let essai = ref 0 in |
|
|
foo |
|
(essai := 1 ; essai) |
|
(essai := 2 ; essai);; |
|
|
|
|
E1 |
|
E2 |
-
4 si l'ordre d'évaluation est foo puis E1 puis
E2 ou bien
- 2 l'ordre d'évaluation est si E2 puis E1 puis foo
Ainsi lorsqu'une application est totale il n'y a pas besoin de créer les
fermetures intermédiaires :
Fx1x2...xn = F1x2...xn=Fn-1xn
Les exceptions
Caml-Light offre un mécanisme d'erreurs simple grâce au type exn.
exception Foo ;;
exception Bar of int;;
Les exceptions sont des valeurs :
let to_foo = function an_exn -> match an_exn with
| Bar _ -> Foo
| an_exn -> an_exn ;;
marquées exceptionnelles (déclencher ou lever une exception) par :
raise I
On peut récupérer une exception :
try E with | F ® EI
let v = 1 in
try (let v = 2 in raise Foo)
with
| Foo -> v
;;
Soit E l'environnement courant.
-
Si l'évaluation de E dans E ne
déclenche pas d'exception c'est une valeur V (de type sv) qui est le
résultat.
- Sinon, soit I l'exception déclenchée, le résultat de
l'évaluation est alors le même que celui de
match I with | F ® EI
dans l'environnement E.
En particulier les motifs des clauses Fi doivent être des exceptions
et les corps des clauses
EIi doivent avoir des types compatibles avec sv.
Le filtrage n'a pas besoin d'être exhaustif,
on peut récupérer n'importe quelle exception.
c'est à éviter
Une exception marquée n'est plus une valeur :
let foo = function x ->
try (x+1) with
| Foo -> -1
;;
foo (raise Foo);;
Traits impératifs
Les références Caml-Light correspondent aux variables des langages
impératifs. Ce sont des ``boites'' dans lesquelles on peut ``mettre'' des
valeurs de type fixé.
Caml-Light |
C |
let r = ref 1 |
int r = 1 |
On distingue la référence de son contenu |
r := !r + 1 |
r = r + 1 |
On a alors un modèle dynamique de mémoire analogue aux environnements
mais dont les ``clés'' sont des références.
L'affectation
(prefix :=):aref ->a -> unit
modifie la valeur associée à la clé, la création
ref :a->aref
-
rajoute une nouvelle référence dans l'environnement,
-
crée une entrée associée à cette valeur dans la mémoire.
Soit M l'état mémoire d'un programme et E l'environnement courant ,
let r = ref v introduit
-
un nouvel environnement
E' =ë(r=vr)<|Eû
où vr est une nouvelle
valeur telle que r -E'-> vr
- un nouvel état mémoire M'=é(vr¬v)<|Mù.
on dit parfois que vr est un pointeur sur v ou l'adresse de
v.
Si r -E-> vr
-
on consulte le contenu de vr par !r et on note M|>vr
-
l'instruction r := v' ne modifie pas
l'environnement courant E mais affecte la mémoire courante M :
é(M|>vr)¬v'ù où v' -E-> v'
Il n'y a pas de masquage dans la mémoire :
é(M|>vr)¬v'ù |
= |
é(é(vr¬v)<|M'ù|>vr)¬v'ù |
= |
é(vr¬v')<|M'ù |
L'état d'un programme est alors donné par le couple (E, M).
Les vecteurs (vect), enregistrements (record) dont les
champs sont déclarés mutables (mutable) suivent le même modèle.
let foo =
let c = ref 0 in
function () ->
begin c := !c + 1 ; !c end ;;
incrémente un compteur local à la fonction à chaque appel.
let foo = function () ->
let c = ref 0 in
begin c := !c + 1 ; !c end ;;
Crée un nouveau compteur à chaque appel.
Que fait ? function c -> begin c := !c + 1 ; c end
La séquence, les boucles
l'expression |
begin |
E1; |
... |
En-1; |
En |
end |
|
équivaut à |
let void = E1 in |
let void = ... in |
let void = En-1 in |
En |
|
La boucle
for i = Im to IM do E done
introduit un nouvel identificateur i : int.
Dans un environnement E, si Im -E-> im et
IM -E-> iM, on évalue successivement l'expression
E dans les environnements Ei =ë(i=i)<|Eû
pour i Î im... iM
et on retourne la valeur ().
let i = "i" ;;
for i = 1 to 9
do
begin
print_int i ;
print_newline() ;
i
end
done ;;
i;;
l'identificateur i n'est ainsi pas lié en sortie de boucle !
Impératif/Fonctionnel
let fact = function n ->
let res = ref 1 in
begin
for i = 1 to n do
res := !res * i
done ;
!res
end;;
|
let rec fact = function
| 0 -> 1
| n -> n * (fact (n-1));;
Consommation de pile !
let fact =
let rec f_r = fun r n ->
match n with
| 0 -> r
| _ -> f_r (n*r) (n-1)
in
function n -> f_r 1 n;;
|
les paramètres
n et
r sont analogues à
i et
!res
Impératif/Fonctionnel
let fib = function n ->
let f_0 = ref(0)
and f_1 = ref(1)
and temp = ref(0) in
begin
for i = 1 to n do
temp := !f_0;
f_0 := !f_1;
f_1 := !temp + !f_1
done;
!f_0
end ;;
|
let fib =
let rec f_r = function
| 0 -> (0, 1)
| n ->
let (f_0,f_1) =
f_r(n - 1)
in (f_1, f_0 + f_1)
in fun n -> fst(f_r n) ;;
Comment rendre terminal l'appel
f_r(n - 1) |
Correction de programmes
-
Terminaison de fonctions récursives
let rec ack = function n -> function m ->
if (n = 0)
then
m + 1
else
if (m = 0)
then
ack (n - 1) 1
else
ack (n - 1) (ack n (m - 1)) ;;
-
Démonstration de propriétés
let min_list = function l -> match l with
| [] -> failwith "Liste vide"
| h::t -> it_list min h t ;;
Équivalent à
let min_list =
let rec aux = function m -> function
| [] -> m
| h::t ->
if (h < m)
then aux h t
else aux m t
in
function l -> match l with
| [] -> failwith "Liste vide"
| h::t -> aux h t ;;
Terminaison
Mathématique |
Caml-Light |
Ensemble inductif |
type récursif |
fonction récursive |
fonction récursive |
type arbre_bin =
| Feuille of int
| Noeud of (arbre_bin * arbre_bin) ;;
let rec it_arbre = fun op -> function arbre ->
match arbre with
| Feuille n -> n
| Noeud (fg,fd) ->
op (it_arbre op fg) (it_arbre op fd) ;;
On n'explicite pas les environnements, les expressions, les
valeurs !
On identifie les fermetures et leurs noms, les paramètres et leurs valeurs
Par induction on a deux cas :
-
l'arbre est une feuille et
it_arbre(F, Feuillen) vaut n
- on suppose que A1 et A2 sont des arbre_bin tels que :
-
it_arbre(F , A1) = a1
-
it_arbre(F,A2) = a2
La valeur de
it_arbre(F,Noeud(A1,A2))
est alors F(a1,a2).
Correction
let rec append = function l ->
match l with
| [] -> function l -> l
| h::t -> function l -> h::(append t l) ;;
A-t-on l'associativité :
appendl1(appendl2l3) =
append(appendl1l2)l3
-
si l1 est vide,
appendl1(appendl2l3) = appendl2l3 et
append(appendl1l2)l3 = appendl2l3
- sinon l1 = h1::l1' et
appendl1(appendl2l3) |
= |
h1::(appendl1'(appendl2l3)) |
append(appendl1l2)l3 |
= |
append(h1::(appendl1'l2))l3 |
|
= |
h1::(append(appendl1'l2)l3) |
On conclut par induction en utilisant le résultat sur l1', l2 et l3.
Exemple : le pgcd
let rec gcd = function n -> function m ->
if (m = 0)
then n
else gcd m (n mod m) ;;
Termine pour m et n positifs ?
-
supposons m < n.
-
Si m=0 le résultat est correct.
- Sinon on a 0 £ (nmodm) < m et
nmodm < m
et le couple (n,m) décroît strictement pour l'ordre produit.
- si m ³ n alors
m < (nmodm)3
et on utilise le point précédent.
Tout diviseur commun à n et m divise le pgcd :
Si d divise n et m alors n = d n' et m = d m' et on a :
nmodm |
= |
(d n')mod(d m') |
|
= |
d(n'modm') |
en supposant le bon fonctionnement de mod
et d divise (nmodm).
- 3
- sauf si m=n=0 mais le résultat reste correct
Ce document a été traduit de LATEX par
HEVEA.