[couverture de Linux Magazine 65]

Perles de Mongueurs (6)

Article publié dans Linux Magazine 65, octobre 2004.

[+ del.icio.us] [+ Developers Zone] [+ Bookmarks.fr] [Digg this] [+ My Yahoo!]

Tables de distribution (dispatch tables)

Imaginons que vous programmez un jeu de rôle, un robot ou même simplement un CGI qui réagit de manière complètement différente en fonction des paramètres qu'il reçoit. La manière naïve de programmer son comportement est d'utiliser une longue suite d'instructions if ... elsif ... else.

Voici un exemple de code ainsi conçu :

    # la routine action() est appelée avec le nom de la commande
    # à exécuter et la liste de ses paramètres
    sub action {
        my ($cmd, @args) = @_;

        if( $cmd eq 'ouvrir' ) {
            ...
        }
        elsif( $cmd eq 'quitter' ) {
            print "Au revoir!\n";
            exit;
        }
        else {
            # commande inconnue
            print "Commande <$cmd> inconnue !\n";
        }
    }

Ce genre de programme pose plusieurs problèmes :

Il est souvent souhaitable de ne pas manipuler directement le code des fonctions, mais de se contenter de ne manipuler que des fonctions. C'est ce que permet la notion de callback (fonction de rappel) : une routine prend des références à d'autres routines en paramètre et se charge de les appeler avec les bons arguments. Voici un exemple simple :

    # action_chaines() prend en paramètre une chaîne de caractères
    # et une liste de fonctions à appliquer dessus, dans l'ordre.
    # Chaque fonction renvoie une version modifiée de la chaîne reçue.
    sub actions_chaine {
        my ( $str, @func ) = @_;

        $str = $_->($str) for @func;
        return $str;
    }

    # les actions sont appliquées dans l'ordre
    print actions_chaine( "abcdef",
        sub { scalar reverse shift },       # inverse la chaîne
        sub { my $s = shift; chop $s; $s }, # supprime le dernier caractère
        sub { "x-$_[0]" },                  # préfixe par "x-"
    );

Ce qui donne :

   x-fedcb

Il devient alors excessivement simple d'ajouter une action à la file, de manipuler différentes files d'actions de traitement ou de les combiner.

Mais revenons plutôt à notre jeu de rôle (ou programme interactif) pour voir comment utiliser cette notion de fonctions de rappel. Si on crée un hachage ayant pour clés le nom des actions et pour valeur une référence à la fonction correspondante, le code de la fonction action() devient très simple :

    # le hachage peut pointer vers des fonctions définies ailleurs
    # ou des fonctions anonymes
    %actions = (
        ouvrir  => \&ouvrir,
        quitter => sub { print "Au revoir!\n"; exit },
        ...
    );

    # la fonction action() se contente
    # de faire appel à la routine correspondante
    sub action {
        my ( $cmd, @args ) = @_;
        if( exists $actions{$cmd} ) {
            $actions{$cmd}->( @args );
        } else {
            die "L'action <$cmd> n'existe pas !";
        }
    }

On appelle cette structure une table de distribution (dispatch table en anglais). Ajouter une action revient alors à ajouter une clé dans le hachage. Des programmes suffisamment compliqués peuvent même le faire dynamiquement (au cours de leur exécution ou à partir d'un fichier de configuration).

Si on regarde les trois problèmes posés au début, on voit que ce système apporte une solution à chacun d'entre eux :

Dans le cadre d'un autre type de programme, on peut très bien imaginer ajouter des fonctionnalités à notre robot, en lui donnant directement le code Perl correspondant aux actions :

    sub cree_action {
        my ($nom, $code) = @_;

        # construit une routine anonyme
        my $sub = eval "sub { $code }";
        if( $@ ) {
            # la compilation a raté, revoyez votre copie
            print "Impossible de compiler le code: $@\n";
            return;
        }
        else {
            # ajoute l'action à la liste des actions possibles
            # (remplace l'action $nom si elle existe)
            $actions{$nom} = $sub;
            print "Action $nom ajoutée\n";
        }
    }

(Inutile de préciser que ce genre de code constitue à lui seul un énorme trou de sécurité, selon les utilisateurs qui ont accès à votre robot.)

Les tables de dispatch permettent de manipuler une abstraction qui représente l'ensemble des fonctionnalités de notre programme. C'est un mode de fonctionnement extrêmement souple et efficace.

Pour conclure, voici comment ajouter à votre programme la possibilité de gérer des commandes abrégées, quand il n'y a aucune ambiguïté :

    sub action {
        my ( $cmd, @args ) = @_;

        # trouve la liste des commandes commençant par $cmd
        my @possibles = grep { /^$cmd/ } keys %actions;

        if( @possibles == 0 ) {
            # y en a pas
            print "L'action <$cmd> n'existe pas !";
        }
        elsif( @possibles > 1) {
            # y en a plusieurs, impossible de choisir
            print "Action <$cmd> ambigue : @possibles\n";
        }
        else {
            # on a trouvé la seule action possible
            $actions{$possibles[0]}->( @args );
        }
    }

Il suffit d'essayer d'ajouter cette fonctionnalité à la première version de notre routine action() pour se convaincre de l'élégance de ce concept de table de distribution.

(Philippe "BooK" Bruhat, Paris.pm & Lyon.pm - book@mongueurs.net)

Les nouvelles de la communauté Perl

OSCON 2004

L'Open Source Conference organisée par O'Reilly a eu lieu du 26 au 30 juillet 2004. Larry Wall a reçu la récompense de 10 000 dollars, non pour Perl, mais pour un logiciel beaucoup plus ancien mais aussi indispensable : patch.

[Photographie montrant Nicholas Clark qui vient d'entarter Dan Sugalski]
Photo Derrick Story, O'Reilly Media.

Deux participants de YAPC::Europe 2003 (Paris) se sont illustrés à OSCON. Dan Sugalski a perdu son pari d'utiliser la machine virtuelle Parrot pour faire tourner du bytecode Python plus rapidement que l'interpréteur Python. Les performances étaient correctes mais Leopold Tötsch et lui n'ont pas eu le temps d'implémenter tous les tests. Rappellons que la machine virtuelle Parrot est développée pour pouvoir exécuter le bytecode du futur Perl6. Quitte à être entarté par Guido Von Rossum, l'auteur de Python, Dan a proposé une mise aux enchères au profit de TPF (The Perl Foundation) pour un second entartage. Nicholas Clark, portant le T-shirt YAPC::Europe 2003 s'est fait un plaisir d'entarter Dan. Nicholas travaille notamment sur Ponie, un projet pour exécuter du code Perl5 sur Parrot.

Quelques liens pour vous cultiver :

(Stéphane Payrard, Paris.pm - stef@mongueurs.net)

À vous !

Envoyez vos perles à perles@mongueurs.net, elles seront peut-être publiées dans un prochain numéro de Linux Magazine.

[IE7, par Dean Edwards] [Validation du HTML] [Validation du CSS]