• Aucun résultat trouvé

Fonctionnelles et polymorphisme

4.6 La pleine fonctionnalit´ e

Nous avons vu que les fonctions peuvent ˆetre pass´ees en arguments ou rendues en r´esultat, comme toutes les autres donn´ees. Plus ´etonnant encore, on les manipule comme des valeurs ordinaires `a l’int´erieur des structures de donn´ees. Nous ´etudions maintenant un exemple qui nous am`ene tr`es naturellement `a utiliser des tableaux de fonctions.

Menu `a deux cas

Notre but est d’´ecrire une fois pour toutes une proc´edure qui affiche un menu, lit le choix de l’utilisateur et lance l’option correspondante du menu. Pour simplifier, nous nous restreignons dans un premier temps aux menus qui offrent exactement deux possi- bilit´es. La proc´edure prend donc quatre arguments : deux messages d’invite `a afficher et deux proc´edures correspondantes. Apr`es avoir affich´e le menu, elle lit l’option retenue par l’utilisateur `a l’aide de la fonction pr´ed´efinie read_int, qui lit un entier tap´e au clavier, puis appelle l’option correspondante, en testant la r´eponse de l’utilisateur.

# let menu invite1 option1 invite2 option2 =

print_string ("<0>: " ^ invite1); print_string " "; print_string ("<1>: " ^ invite2); print_newline (); print_string "Choisissez votre option: ";

let r´eponse = read_int () in

if r´eponse = 0 then option1 () else option2 ();;

Pour nos essais, nous d´efinissons deux petites proc´edures qui impriment simplement un message au terminal :

# let au_revoir () = print_string "Au revoir"; print_newline ();; au_revoir : unit -> unit = <fun>

# let continuer () = print_string "Continuons!"; print_newline ();; continuer : unit -> unit = <fun>

Nous obtenons alors le dialogue suivant :

# menu "Arr^eter" au_revoir "Continuer" continuer;; <0>: Arr^eter <1>: Continuer Choisissez votre option: 1 Continuons!

- : unit = ()

Menu `a plusieurs cas

Pour g´en´eraliser la proc´edure pr´ec´edente `a un nombre quelconque d’options, il suffit de lui passer deux tableaux en arguments : un tableau de chaˆınes de caract`eres pour les messages d’invite et un tableau de proc´edures pour les options. Il faut maintenant ´ecrire le menu avec une boucle for parcourant le tableau des messages, puis lire l’option choisie par l’utilisateur et s´electionner la proc´edure correspondante du tableau des options.

# let menu invites options =

for i = 0 to vect_length invites - 1 do print_string

("<" ^ (string_of_int i) ^ ">: " ^ invites.(i) ^ " ") done;

print_newline ();

print_string "Choisissez votre option: "; let r´eponse = read_int () in

options.(r´eponse) ();;

menu : string vect -> (unit -> ’a) vect -> ’a = <fun>

La fonction pr´ed´efinie string_of_int renvoie la chaˆıne de caract`eres correspondant `a son argument entier.

`

A titre d´emonstratif, nous appelons la proc´edure avec une troisi`eme option qui con- siste `a ne rien faire : la proc´edure associ´ee est simplement la fonction identit´e (sp´ecialis´ee au type unit) que nous fournissons comme une fonction anonyme.

# menu [| "Arr^eter"; "Continuer"; "Ne rien faire" |] [| au_revoir; continuer; (function () -> ()) |];; <0>: Arr^eter <1>: Continuer <2>: Ne rien faire

Choisissez votre option 2 - : unit = ()

Utiliser les types pour ´eviter les erreurs

Fournir deux tableaux distincts pour les options et les messages d’invite est source d’erreurs, puisque le typage n’assure pas la correspondance entre l’invite et l’option. La correction est ais´ee : il suffit de n’utiliser qu’un seul tableau contenant des paires dont

La pleine fonctionnalit´e 69 le premier ´el´ement est un message d’invite et le second l’option associ´ee. Cet exemple nous am`ene `a d´efinir les fonctions d’acc`es aux composantes d’une paire, traditionnelle- ment nomm´ees fst (pour first, qui signifie « premier » en anglais) et snd (pour second,

«second »). Bien que ces fonctions soient pr´ed´efinies en Caml, nous ´ecrivons leur code car il est ´el´egant. On op`ere tout simplement par filtrage de la paire argument :

# let fst (x, y) = x;; fst : ’a * ’b -> ’a = <fun> # let snd (x, y) = y;; snd : ’a * ’b -> ’b = <fun>

Une fois de plus, le polymorphisme nous autorise `a d´efinir ces deux fonctions pour tous les types de paires. La fonction menu est maintenant sans surprises.

# let menu invites_options =

for i = 0 to vect_length invites_options - 1 do print_string ("<" ^ (string_of_int i) ^ ">: "); print_string (fst (invites_options.(i)) ^ " ") done;

print_newline ();

print_string "Choisissez votre option: "; let r´eponse = read_int () in

(snd (invites_options.(r´eponse))) ();;

menu : (string * (unit -> ’a)) vect -> ’a = <fun> # menu [| ("Arr^eter", au_revoir);

("Continuer", continuer);

("Ne rien faire", (function () -> ())) |];; <0>: Arr^eter <1>: Continuer <2>: Ne rien faire

Choisissez votre option: 0 Au revoir

- : unit = ()

Un menu polymorphe tr`es g´en´eral

R´efl´echissons encore un peu sur la proc´edure menu : la quintessence de cette proc´edure n’est pas d’appliquer directement les options, mais plutˆot de retourner un certain ´el´ement d’un tableau d’options, selon la r´eaction de l’utilisateur aux propositions affich´ees. Un pas de plus dans la g´en´eralisation consiste donc `a ne pas consid´erer que les options doivent forc´ement ˆetre des proc´edures. On se contente alors de retourner le deuxi`eme ´el´ement du couple correspondant au message d’invite choisi par l’utilisateur.

# let menu invites_options =

for i = 0 to vect_length invites_options - 1 do print_string ("<" ^ (string_of_int i) ^ ">: "); print_string (fst (invites_options.(i)) ^ " "); print_string " "

done;

print_newline ();

print_string "Choisissez votre option"; let r´eponse = read_int () in

menu : (string * ’a) vect -> ’a = <fun>

Ainsi, la proc´edure menu retourne aussi bien des entiers que des fonctions. Voici par exemple un morceau de programme qui d´eterminerait le niveau de difficult´e `a prendre en compte dans un jeu. Ici la fonction menu retourne un entier.

# let niveau_de_difficult´e =

print_string "^Etes-vous"; print_newline (); menu [| ("D´ebutant ?", 1); ("Amateur ?", 2); ("Amateur confirm´e ?", 5); ("Expert ?", 10) |];; ^ Etes-vous

<0>: D´ebutant ? <1>: Amateur ? <2>: Amateur confirm´e ? <3>: Expert ? Choisissez votre option: 0

niveau_de_difficult´e : int = 1

Nous avons cependant toujours le loisir d’appeler menu avec des options fonctionnelles.

# let option =

menu [| ("Arr^eter", au_revoir); ("Continuer", continuer);

("Ne rien faire", (function () -> ())) |] in option ();;

<0>: Arr^eter <1>: Continuer <2>: Ne rien faire Choisissez votre option: 0

Au revoir - : unit = ()

Il est bien entendu que la fonction menu reste na¨ıve : il lui faudrait tester la validit´e de la r´eponse de l’utilisateur et l’interroger `a nouveau en cas d’erreur. La validation de la r´eponse pourrait s’effectuer `a l’aide d’une fonction, argument suppl´ementaire de menu. On peut aussi envisager de lire des chaˆınes de caract`eres au lieu de nombres (par exemple "oui" ou "non"). Il n’en demeure pas moins que le polymorphisme et la pleine fonctionnalit´e nous permettent d’´ecrire une fonction tr`es g´en´erale dans laquelle les probl`emes de mise en page des menus, d’obtention d’une r´eponse et de validation de la r´eponse obtenue seront factoris´es une fois pour toutes.

Vous en savez maintenant assez pour passer au chapitre suivant. Ce qui suit est ´etonnant mais technique. En particulier, nous verrons que le langage est assez puissant pour d´efinir un moyen automatique de passer de la version curryfi´ee `a la version non curryfi´ee d’une fonction.