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 .