mardi 2 octobre 2007

Types fantômes et typage fort

La plupart des programmes un peu gros font une utilisation massive d'identifiants pour référencer des entités. C'est particulièrement le cas pour un MMORPG: chaque joueur a un identifiant, chaque personnage, chaque monstre, chaque type d'objet, chaque objet, peut-être chaque ligne de dialogue, et bien d'autres encores, sont référencés via des nombres, qui sont utilisés en interne, dans la base de données, et dans les messages envoyés au client. L'on aura par exemple un message du type "Le personnage 23576 appartenant au joueur 17980 a pris l'objet 7894 de type 45 et l'a mis à la position 17 du sac 3 de son inventaire".

La multiplication des identifiants augmente les risques de les confondre les uns avec les autres, ce qui peut engendrer des bugs particulièrement retors. Une solution assez standard dans l'industrie consiste donc à "habiller" notre entier avec une information supplémentaire, le discriminant, qui n'est là que pour s'assurer que l'on ne mélange pas les tomates avec les carrottes.

Dans un language statique orienté objet, l'on va par exemple créer une classe par type d'identifiant, en surchargeant si le language l'autorise certains opérateurs, pour l'assurer que toute comparaison / tout assignement entre des types différents va engendrer une erreur à la compilation. L'on pourra même en C++ concevoir une classe template qui prendra le type discriminant comme paramètre. L'on pourrait alors utiliser la classe comme suit:


class DiscriminantJoueur;
Class DiscriminantPersonnage;

typedef TypageFort JoueurIdT;
typedef TypageFort PersonnageIdT;

JoueurIdT joueur1(5);
JoueurIdT joueur2(6);
JoueurIdT joueur3(5);

PersonnageIdT personnage1(5);

assert(joueur1 == joueur3);
assert(joueur1 =! joueur2);
joueur1 == personnage1; // Erreur de compilation! Un joueur n'est pas un personnage

std::set joueurSet;
joueurSet.insert(joueur1);
joueurSet.insert(joueur2);
joueurSet.insert(personnage1); // Erreur de compilation!


Pour ceux que cela intéresse, AdH propose une implémentation en C++ d'une telle classe. Vous pouvez voir le code en ligne ici.

Les types fantômes en Ocaml permettent de fournir la même sécurité très simplement. Voici une implémentation d'un module permettant le typage fort d'entiers (Note: le code est complet, il est possible de directement copier/coller dans un top-level ocaml):


module T :
sig
type 'a t
val to_int : 'a t -> int
val from_int : int -> 'a t
end
=
struct
type 'a t = int
let to_int e = e
let from_int e = e
end;;


Le type fantôme est cet étrange type 'a t, qui a une signature dépendante d'un type quelconque 'a, mais qui en réalité est un simple entier. Passer du type 'a t à un entier et inversement est donc simplement l'identité. Cependant, ce 'a sera le discriminant de notre type. Voici un exemple d'utilisation:


let joueur1 : [`JOUEURID] T.t = T.from_int 5;;
let personnage1 : [`PERSONNAGEID] T.t = T.from_int 5;;
let personnage2 : [`PERSONNAGEID] T.t = T.from_int 6;;
let personnage3 : [`PERSONNAGEID] T.t = T.from_int 5;;

personnage1 = personnage2;;
- : bool = false
personnage1 = personnage3;;
- : bool = true

personnage1 = joueur1;;
This expression has type [ `PERSONNAGEID] T.t
but is here used with type [ `JOUEURID] T.t
These two variant types have no intersection


Bien sûr, l'ajout à une liste (ou toute autre structure de données) fonctionnera de la même manière:


let persoliste = [personnage1; personnage2];;
val persoliste : [ `PERSONNAGEID ] T.t list = [; ]
let persoliste = joueur1::persoliste;;
This expression has type [ `PERSONNAGEID ] T.t list
but is here used with type [ `JOUEURID ] T.t list
These two variant types have no intersection


En conclusion, les types forts, c'est bon, mangez en! En ocaml, de préférence :)

Aucun commentaire: