• Aucun résultat trouvé

6.3 Avantages du prototypage en Perl

7.1.1 Structures de poids

On commence par d´ecrire la structure de poids car c’est sans doute le composant le plus susceptible d’ˆetre modifi´e. Si le anneau tropical est le plus courant en TALN, on a bien vu que tout demi-anneau pouvait servir de poids dans une s´erie rationnelle. On d´efinit donc une classe de poids g´en´erique

124 Une biblioth`eque exp´erimentale de calcul `a ´etats finis pond´er´es

Fig. 7.1 – Hi´erarchie des classes du module WFST

(qui serait plus ou moins l’´equivalent d’une classe virtuelle en C++ ou d’une interface en Java) dont les classes de poids sp´ecifiques (demi-anneau tropical, r´eels, bool´eens) h´eritent.

La devise de Perl ´etant « there is more than one way to do it » (« il y a plus d’une fa¸con de le faire »), la fa¸con exacte de d´efinir une classe est tr`es libre. Pour toutes les classes, on suit le mˆeme mod`ele que l’on illustre ci-dessous avec la classe de poids WFST::Weight.

package WFST::Weight;

use overload "+" => "wadd", "*" => "wmul", "/" => "wdiv", "<=>" => "wcmp", "0+" => "wval"; use vars qw(@ISA);

@ISA = qw(WFST);

La classe WFST::Weight est d´efinie comme h´eritant de WFST grˆace `a la variable de classe @ISA (pour « is a », « est un(e) »). Diff´erents op´erateurs sont surcharg´es grˆace au pragmaoverload, chacun correspondant `a une fonction sur les poids d´efinie plus loin. Les deux plus int´eressants sont l’addition et la multiplication, qui correspondent respectivement aux m´ethodes wadd et wmul d´efinies pour chaque sous-classe.

La d´efinition d’une classe passe ensuite par la d´efinition de sa repr´esentation et des m´ethodes qui lui sont associ´ees. Un poids est simplement un scalaire (qui peut aussi bien ˆetre un nombre entier ou r´eel qu’une chaˆıne de caract`eres). Dans Perl, un objet est toujours une r´ef´erence (vers un scalaire ou vers un tableau) qui est ensuite b´enie par la fonction bless. C’est la fonction new qui cr´ee un nouvel objet poids, en prenant pour argument la classe ou une instance de cette classe (param`etre $self) et le poids (param`etre $weight, qui est soit un scalaire, soit un autre poids).

sub new {

my ($self, $weight) = @_; if (ref $self) {

$weight = $$self if !defined $weight; bless \$weight, ref $self;

} else {

$weight = $self->one if !defined $weight; bless \$weight, $self;

} }

Comme pour toutes les autres classes de WFST.pm, la m´ethode new est une m´ethode d’instance et de classe, c’est-`a-dire qu’un poids peut ˆetre cr´e´e de deux fa¸cons possibles :

7.1 D´efinition des structures de donn´ees 125

$w = new WFST::Weight(1); $w2 = $w->new(2);

Trois variables de classe sont ´egalement d´efinies. Il faut tout d’abord d´efinir deux poids sp´eciaux, qui sont le poids nul et le poids unitaire (les ´el´ements 0 et 1 ), repr´esent´es respectivement par les variables $zero et $one. Enfin, une variable suppl´ementaire contient la d´efinition de la syntaxe d’un poids qui se greffe sur la grammaire d´ecrivant la syntaxe d’une expression r´eguli`ere. Cette variable s’appelle $grammar. Trois m´ethodes, zero, one et grammar, permettent d’acc´eder `a ces trois variables de classes. On les d´efinit de mani`ere un peu « magique », de fa¸con `a pouvoir les red´efinir automatiquement dans les classes qui h´eritent de WFST::Weight :

do {

no strict "refs";

for my $var qw(one zero grammar) {

*$var = sub { ${(ref $_[0] || $_[0]) . "::$var"} }; }

};

Si les fonctions zero, one et grammar sont d´efinies et renvoient la valeur correspondant `a leur nom, les variables elles-mˆemes ne sont pas d´efinies.

Enfin, les diff´erentes op´erations sont d´efinies par les fonctions wadd (addition), wmul (multipli-cation), wdiv (division), wcmp (comparaison) et wval (qui renvoie simplement la valeur scalaire du poids). Les deux derni`eres sont d´ej`a d´efinies, par contre les trois premi`eres doivent obligatoirement ˆetre red´efinies par les classes qui h´eritent de WFST::Weight comme on va le voir plus bas.

sub wadd {} sub wmul {} sub wdiv {} sub wcmp { (${$_[0]} <=> (ref $_[1] ? ${$_[1]} : $_[1])) * ($_[2] ? -1 : 1) } sub wval { ${$_[0]} }

On note pour finir que le premier param`etre de chacune de ces fonctions est toujours une instance de poids, mais que le second peut ˆetre soit un poids, soit un scalaire.

7.1.1.1 Le demi-anneau tropical

La d´efinition du demi-anneau tropical est la suivante : package WFST::Weight::Tropical;

use vars qw(@ISA $one $zero $grammar); @ISA = qw(WFST::Weight);

$zero = new WFST::Weight::Tropical "inf"; $one = new WFST::Weight::Tropical 0; $grammar = q{

weight : /\-?\d*\.?\d+/

{ new WFST::Weight::Tropical($item[1]) } };

sub wadd { $ {$_[0]} < (ref $_[1] ? $ {$_[1]} : $_[1]) ? $_[0] : $_[1] } sub wmul { $_[0]->new($ {$_[0]} + (ref $_[1] ? $ {$_[1]} : $_[1])) } sub wdiv { $_[0]->new($ {$_[0]} - (ref $_[1] ? $ {$_[1]} : $_[1])) }

L’addition est en r´ealit´e la fonction min qui renvoie le minimum de deux valeurs ; la multiplication est en fait l’addition, et la division n’est autre que la soustraction. Logiquement, les variables zero

126 Une biblioth`eque exp´erimentale de calcul `a ´etats finis pond´er´es

et one sont d´efinies `a ∞ et 0 respectivement. La variable grammaire contient une r`egle qui d´efinit un poids comme un nombre r´eel.

7.1.1.2 Le demi-anneau r´eel

Dans le mˆeme ordre d’id´ee, le demi-anneau r´eel est d´efini comme suit : package WFST::Weight::Real;

use vars qw(@ISA $one $zero $grammar); @ISA = qw(WFST::Weight);

$zero = new WFST::Weight::Real 0; $one = new WFST::Weight::Real 1; $grammar = q{

weight : /\-?\d*\.?\d+/

{ new WFST::Weight::Real($item[1]) } };

sub wadd { $_[0]->new($ {$_[0]} + (ref $_[1] ? $ {$_[1]} : $_[1])) } sub wmul { $_[0]->new($ {$_[0]} * (ref $_[1] ? $ {$_[1]} : $_[1])) } sub wdiv { $_[0]->new($ {$_[0]} / (ref $_[1] ? $ {$_[1]} : $_[1])) }

7.1.1.3 Le demi-anneau bool´een

Enfin, le demi-anneau bool´een qui utilise des entiers (Perl ne d´efinit pas de vrais bool´eens : comme en C, une valeur nulle est fausse et une valeur non-nulle est vraie) :

package WFST::Weight::Boolean;

use vars qw(@ISA $one $zero $grammar); @ISA = qw(WFST::Weight);

$zero = new WFST::Weight::Boolean 0; $one = new WFST::Weight::Boolean 1; $grammar = q{

weight : /[01]/

{ new WFST::Weight::Boolean($item[1]) } };

sub wadd { $_[0]->new($ {$_[0]} || (ref $_[1] ? $ {$_[1]} : $_[1])) } sub wmul { $_[0]->new($ {$_[0]} && (ref $_[1] ? $ {$_[1]} : $_[1])) } sub wdiv { $_[0]->new($ {$_[0]} && !(ref $_[1] ? $ {$_[1]} : $_[1])) }

Ici, l’op´eration additive est le « ou » logique (dont l’´el´ement neutre est 0) et l’op´eration multiplica-tive est le « et » logique (dont l’´el´ement neutre est 1). On a d´efini la division comme la multiplication par l’inverse, soit a ∧ b .