Fonctionnelles et polymorphisme
6.1 Polynˆ omes pleins et polynˆ omes creu
Nous avons vu par deux fois des calculs sur les polynˆomes, d’abord repr´esent´es par des tableaux dans le chapitre 3 (section 3.3), puis comme des listes dans le chapitre 5 (section 5.5). Nous avons appel´e les seconds polynˆomes creux, les premiers polynˆomes pleins. Maintenant se pose ´evidemment le probl`eme de travailler avec ces deux repr´esentations en mˆeme temps, pour b´en´eficier des avantages de chacune d’elles : lorsqu’un polynˆome est plein, la repr´esentation `a l’aide d’un tableau est ´economique, car les degr´es sont implicites ; en revanche, lorsqu’un polynˆome est creux (comporte beaucoup de coefficients nuls), la repr´esentation en liste est pr´ef´erable — quand elle n’est pas tout simplement la seule envisageable, comme pour le polynˆome 1 + x1000000.
Nous aimerions donc repr´esenter les polynˆomes par un tableau ou une liste selon le cas, mais d´efinir des op´erations qui travaillent indiff´eremment sur l’une ou l’autre des repr´esentations. Or, ces deux types de repr´esentations sont incompatibles au point de vue du typage. Consid´erons la proc´edure d’impression des polynˆomes : nous avons d´efini deux fonctions, sp´ecifiques `a chacune des repr´esentations, imprime_polyn^ome_plein : int vect -> unit, qui imprime les polynˆomes pleins, et imprime_polyn^ome_creux : (int * int) list -> unit, qui imprime les polynˆomes creux. Pour avoir une primitive d’impression travaillant sur tous les polynˆomes, on aurait donc envie d’´ecrire :
let imprime_polyn^ome p = if p «est un polynˆome plein»
else imprime_polyn^ome_creux p;;
C’est effectivement la bonne id´ee, mais il faut la raffiner un peu : outre qu’on ne voit pas comment impl´ementer le pr´edicat « est un polynˆome plein », il se pose ´egalement un probl`eme de typage pour l’argument p de imprime_polyn^ome: est-ce une liste comme le sugg`ere l’appel de fonction imprime_polyn^ome_creux p, ou un tableau pour pouvoir ˆetre pass´e en argument `a imprime_polyn^ome_plein? On obtiendrait forc´ement une erreur de typage. Par exemple, en supposant que « est un polynˆome plein » renvoie toujours la valeur true :
# let imprime_polyn^ome p =
if true then imprime_polyn^ome_plein p else imprime_polyn^ome_creux p;; Entr´ee interactive:
> else imprime_polyn^ome_creux p;;
> ^
Cette expression est de type int vect,
mais est utilis´ee avec le type (int * int) list.
Il faut donc m´elanger les polynˆomes creux et pleins au sein d’un mˆeme type qui les comprenne tous les deux.
Le type polyn^ome
On d´efinit donc un nouveau type, polyn^ome, qui ´etablit explicitement le m´elange : il indique qu’il comprend deux cas possibles, le cas des polynˆomes pleins qui seront des tableaux d’entiers et le cas des polynˆomes creux qui seront des listes de paires d’entiers.
# type polyn^ome =
| Plein of int vect
| Creux of (int * int) list;; Le type polyn^ome est d´efini.
Le mot-cl´e type introduit la d´efinition du nouveau type polyn^ome. Apr`es le signe =, on ´ecrit la liste des possibilit´es du type en cours de d´efinition. Les noms Plein et Creux sont appel´es les constructeurs de valeurs du type (s’il n’y a pas d’ambigu¨ıt´e on dit simplement « constructeurs »). Comme d’habitude, la barre verticale | indique l’alternative et se lit « ou ». Le mot-cl´e of indique le type de l’argument du constructeur. Le type polyn^omecomprenant les valeurs d’un type plus les valeurs d’un autre type, on dit que c’est un type somme. On peut maintenant cr´eer des valeurs de type polyn^ome en appliquant l’un des deux constructeurs du type polyn^ome `a une valeur du type correspondant. Par exemple :
# let p1 = Plein [|1; 2; 3|];; p1 : polyn^ome = Plein [|1; 2; 3|] # let p2 = Creux [(1, 0); (1, 100)];; p2 : polyn^ome = Creux [1, 0; 1, 100]
Maintenant p1 et p2 sont du mˆeme type et pourront ˆetre arguments d’une mˆeme fonction.
Polynˆomes pleins et polynˆomes creux 111 Le filtrage est ´etendu `a tous les types somme et permet, ´etant donn´ee une valeur du type somme, de d´eterminer dans quel cas se trouve cette valeur. Pour le type polyn^ome, le filtrage va donc nous permettre d’impl´ementer la fonction « est un polynˆome plein » :
# let est_un_polyn^ome_plein = function | Plein _ -> true
| Creux _ -> false;;
est_un_polyn^ome_plein : polyn^ome -> bool = <fun>
Une fonction travaillant sur des valeurs de type polyn^ome fera typiquement une dis- crimination sur les valeurs du type par un filtrage du genre :
let f = function | Plein v -> ... | Creux l -> ...;;
Remarquez que le filtrage permet `a la fois de d´eterminer le type du polynˆome et de r´ecup´erer son tableau ou sa liste de monˆomes. C’est strictement analogue au cas des listes o`u nous ´ecrivions :
let f = function | [] -> ...
| x :: reste -> ...;;
C’est maintenant un jeu d’enfant que d’´ecrire la fonction d’impression des valeurs de type polyn^ome:
# let imprime_polyn^ome = function
| Plein v -> imprime_polyn^ome_plein v | Creux l -> imprime_polyn^ome_creux l;; imprime_polyn^ome : polyn^ome -> unit = <fun> # imprime_polyn^ome p1;;
1 + 2x + 3x^2- : unit = () # imprime_polyn^ome p2;; 1 + x^100- : unit = ()
Op´erations sur les valeurs de type polyn^ome
Nous d´efinissons l’addition et la multiplication des polynˆomes creux ou pleins. Puisque les polynˆomes se pr´esentent sous deux formes, nous avons quatre cas `a en- visager. L’id´ee est simple :
• la somme de deux polynˆomes creux est un polynˆome creux : on appelle l’addition des polynˆomes creux ;
• la somme de deux polynˆomes pleins est un polynˆome plein : on appelle l’addition des polynˆomes pleins ;
• la somme de deux polynˆomes d’esp`eces diff´erentes est un polynˆome creux. En effet, si l’un des polynˆomes est creux il comprend beaucoup de z´eros et sa somme avec un autre polynˆome comprendra aussi beaucoup de z´eros en g´en´eral (consid´erez par exemple (1 + x + 3x2) + (1 + x100)). Donc, dans le cas mixte, nous appelons encore l’addition des polynˆomes creux. Puisque l’un des polynˆomes est plein, nous avons be- soin d’une fonction qui transforme un polynˆome plein en polynˆome creux. C’est sans
difficult´e : nous parcourons le tableau des coefficients en accumulant dans une liste les monˆomes rencontr´es. La seule subtilit´e est de parcourir le tableau `a l’envers pour que le dernier monˆome ajout´e `a la liste soit bien celui de degr´e 0.
# let plein_vers_creux v = let l = ref [] in
for i = vect_length v - 1 downto 0 do if v.(i) <> 0 then l := (v.(i), i) :: !l done;
!l;;
plein_vers_creux : int vect -> (int * int) list = <fun>
L’addition des polynˆomes se d´efinit alors tr`es simplement :
# let ajoute_polyn^omes p1 p2 = match p1, p2 with
| Plein v, Plein v’ -> Plein (ajoute_polyn^omes_pleins v v’) | Creux l, Creux l’ -> Creux (ajoute_polyn^omes_creux l l’) | Plein v, Creux l ->
Creux (ajoute_polyn^omes_creux (plein_vers_creux v) l) | Creux l, Plein v ->
Creux (ajoute_polyn^omes_creux (plein_vers_creux v) l);; ajoute_polyn^omes : polyn^ome -> polyn^ome -> polyn^ome = <fun>
Ce code peut ˆetre l´eg`erement simplifi´e en remarquant que les deux derniers cas du filtrage sont presque identiques (ces deux cas se traduisent par deux clauses du filtrage dont la partie expression est la mˆeme). Pour ´eviter cette redite, on joue sur le fait que l’addition des polynˆomes est commutative pour traiter le dernier cas par un appel r´ecursif `a la fonction ajoute_polyn^omequi inverse les arguments p1 et p2.
# let rec ajoute_polyn^omes p1 p2 = match p1, p2 with
| Plein v, Plein v’ -> Plein (ajoute_polyn^omes_pleins v v’) | Creux l, Creux l’ -> Creux (ajoute_polyn^omes_creux l l’) | Plein v, Creux l ->
Creux (ajoute_polyn^omes_creux (plein_vers_creux v) l) | Creux l, Plein v ->
ajoute_polyn^omes p2 p1;;
ajoute_polyn^omes : polyn^ome -> polyn^ome -> polyn^ome = <fun>
Cette derni`ere solution permet de ne pas dupliquer de code, ce qui raccourcit l´eg`erement le texte de la fonction et diminue la probabilit´e d’introduire une erreur en ne modifi- ant qu’une des clauses lors de corrections ult´erieures du programme. En fait, lorsque l’expression `a renvoyer est compliqu´ee, l’appel r´ecursif s’impose sans contestation pos- sible. Cependant, cette solution pr´esente l’inconv´enient de sugg´erer que la fonction ajoute_polyn^ome est vraiment r´ecursive, alors qu’elle ne l’est que pour des raisons
«administratives ».
La multiplication n’est pas plus compliqu´ee :
# let rec multiplie_polyn^omes p1 p2 = match p1, p2 with
| Plein v, Plein v’ -> Plein (multiplie_polyn^omes_pleins v v’) | Creux l, Creux l’ -> Creux (multiplie_polyn^omes_creux l l’) | Plein v, Creux l ->
Types sommes ´elabor´es 113
Creux (multiplie_polyn^omes_creux (plein_vers_creux v) l) | Creux l, Plein v ->
multiplie_polyn^omes p2 p1;;
multiplie_polyn^omes : polyn^ome -> polyn^ome -> polyn^ome = <fun> # imprime_polyn^ome (multiplie_polyn^omes p1 p2);;
1 + 2x + 3x^2 + x^100 + 2x^101 + 3x^102- : unit = () # let p10000 = Creux [(1, 0); (1, 10000)];;
p10000 : polyn^ome = Creux [1, 0; 1, 10000]
# imprime_polyn^ome (multiplie_polyn^omes p10000 p10000);; 1 + 2x^10000 + x^20000- : unit = ()