[couverture de Linux Magazine 72]

Perles de Mongueurs (13)

Article publié dans Linux Magazine 72, mai 2005.

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

Le collier de perles de ce mois-ci a été rédigé par Stéphane Payrard (stef@mongueurs.net), de Paris.pm.

Vous pourrez en récolter de nombreuses autres à la prochaine conférence Perl francophone, Les Journées Perl 2005, qui aura lieu à Marseille les 9 et 10 juin 2005.

http://conferences.mongueurs.net/fpw2005/

Chapeau

Perl a été conçu par un programmeur à formation de linguiste, Larry Wall. D'après lui, le langage Perl et les langues naturelles obéissent à des principes similaires. Le principe de Huffman et celui du chunking, que nous exposons, sont incompatibles avec le minimalisme syntaxique aujourd'hui de rigueur dans la conception des langages informatiques. Comme les langues naturelles, ces langages évoluent avec le temps. Nous décrirons l'évolution de Perl1 vers Perl4, jusqu'au futur Perl6.

Larry Wall et la linguistique

Larry s'est appuyé sur sa formation de linguiste pour concevoir Perl, et sur une branche de la pragmatique, la tagmémique. Essayons très rapidement de situer la pragmatique dans le champ de la linguistique. Cela nous aidera à expliquer l'originalité de Perl comparé aux autres langages informatiques.

On nous a enseigné le Français via sa grammaire par des prescriptions et des proscriptions, des règles qui dictent ce qui est le bien parler et le bien écrire, et qui bannissent les mauvaises formes de la langue. Cela s'appelle de la grammaire prescriptive. La linguistique s'intéresse aussi à la grammaire mais vue d'une manière descriptive. Elle s'attache à la langue telle qu'elle est, et non telle qu'une élite voudrait nous la faire parler. Le linguiste aime à miner la linguistique prescriptive et moraliste en exprimant leurs règles de manière auto-contradictoire. Ainsi [1] « Be sure to never split an infinitive » ou « Prepositions are bad to end sentences with ».

La pragmatique, quant à elle, étudie les langues en action et touche aussi à la perception, à la psychologie et aux sciences sociales. Elle s'intéresse à l'efficacité pratique. La notion de contexte y est essentielle. La langue et la parole s'inscrivent dans un tout plus grand.

Il n'est donc pas étonnant que Perl soit différent des autres langages puisqu'il est fondé sur un postulat linguistique. La programmation n'est pas simplement une sous-discipline des mathématiques ou une ingéniérie. Un programme ne s'adresse pas seulement à la machine mais aux hommes qui vont le faire évoluer. Et pour compliquer le tout, les langages, comme les programmes, évoluent.

Larry dans le texte

Après ces considérations générales, laissons Larry parler concrètement des principes qui sous-tendent Perl :

« Qu'un langage soit facile à apprendre est louable, mais le but d'un langage n'est pas de vous aider à apprendre le langage mais de vous aider à apprendre d'autres choses en usant du langage. On n'abêtit pas l'anglais pour le rendre facile à apprendre. Nous préférons que l'anglais reste une langue riche, bizarre, laxiste et redondante. De même pour Perl. Si nous n'abêtissons pas le langage, nous acceptons que les gens parlent un sous-ensemble du langage. Vous pouvez écrire des programmes Perl qui ressemblent à du sed, de l'awk, du C, du Lisp ou du Python. Cela est acceptable dans la culture Perl. »

« Les langues sont fondamentalement amorales. La langue n'est pas le niveau auquel on devrait obliger à "penser bien". On ne peut garantir la moralité par la syntaxe. »

« Beaucoup d'informaticiens sont tombés dans le piège de définir des langages comme la Novlangue d'Orwell dans lequel il est impossible de penser de mauvaises pensées. Ils aboutissent à tuer la créativité dans l'acte de programmer. »

« Je crois qu'un langage doit rester en dehors de tout contrôle, parce que ni une personne, ni une institution n'est capable de contrôler une langue (ou une culture). »

Notez que j'ai traduit language alternativement par langue et langage car l'anglais ne fait pas la distinction entre les deux. Et le propos même de Larry est justement d'affirmer que les mêmes règles ou absences de règles doivent gouverner les languages informatiques et les langues naturelles. Et il utilise indifféremment le vocabulaire de la linguistique et de l'informatique pour parler de l'un ou de l'autre. De même nous utiliserons les mécanismes des langues naturelles pour motiver ceux de Perl.

Pour les anglophones, les pages [2], [3] et [4] sont particulièrement éclairantes sur les opinions de Larry Wall sur la linguistique appliquée à Perl.

Le principe de Huffman

Une des qualités d'une langue naturelle (ou artificielle) est de pouvoir être concise tout en restant compréhensible.

Cela est d'abord vrai au niveau lexical, c'est à dire celui des mots. Les mots les plus usités doivent être les plus courts. C'est vrai aussi au niveau supérieur qui assemble les unités lexicales : le niveau syntaxique.

Dans les langues naturelles comme le français, l'allemand ou l'anglais, les articles et les pronoms sont omniprésents. En anglais, l'article disparaît même purement et simplement pour le pluriel indéfini.

En Perl5, la variable $_ joue le rôle de pronom à tout faire. Mieux, dans bien des cas, on peut l'omettre et écrire print pour print $_, ou m/.../ pour $_ =~ m/.../.

Ce critère de concision pour les élément les plus courants de Perl est appelé par Larry le principe de Huffman, d'après l'auteur du codage éponyme. Ce codage consiste à encoder les lettres les plus usitées avec moins de bits que les plus rares. Le code Morse procédait du même principe mais avec un encodage fixe (et donc forcément non optimal). Dans la communauté Perl, lorsqu'on parle du principe de Huffman, on ne parle pas de l'algorithme spécifique du codage de Huffman, mais du fait que les constructions les plus usitées doivent être les plus courtes.

Les formes conjuguées au présent des verbes être et avoir sont très courtes. Mais elles sont irrégulières. C'est un cas de « Huffmanisation » des langues naturelles. Mais le prix à payer pour un usage commode est un apprentissage plus difficile dû aux irrégularités de la langue.

Le principe de Huffman appliqué au méta-langage

Ce principe de Huffman vaut pour le métalangage, c'est à dire le discours à propos de Perl, comme le présent article.

Les tableaux associatifs sont au cœur de Perl. Dans les langages autres que Perl, on parlera de tables, mais c'est risquer la confusion avec le mot tableau. Perl utilise le mot hash, une métonymie pour parler des tables. Rappellons qu'une métonymie est une figure de style qui désigne une entité par une de ses parties. Ici hash désigne l'algorithme que Perl utilise pour l'implantation des tables associatives. On pourrait utiliser table de hachage, mais ce serait trop long. Entre deux mots, choisir le moindre, voire au prix d'une métonymie et d'un anglicisme.

On parle de regex pour désigner les expressions régulières. De même, on parlera de match plutôt que de correspondance de regex.

Et puisque les articles pluriels fournissent un contexte suffisant, nous invoquerons le Grevisse pour dire que « Ce n'est que dans la mesure où les mots étrangers ne sont pas du tout intégrés au vocabulaire français, où ils constituent une sorte des sortes de citations que l'on peut accepter leur invariabilité [...] ».

Langages à syntaxe minimaliste, gros programmes

L'usage des regex est si fréquent en Perl que la fonction de match a une syntaxe particulière : $var =~ m/.../. Larry ne l'a pas inventée mais l'a reprise de l'utilitaire Unix sed.

Certains concepteurs de langages informatiques préfèrent le minimalisme syntaxique. Cela entraîne de plus gros programmes. Voyons-le dans le contexte des opérations de match. Le match était intégré à la syntaxe de la commande sed. Les langages minimalistes déconstruisent cette intégration du match au nom du minimalisme syntaxique.

Le concepteur minimaliste décide naturellement de ne pas donner de syntaxe particulière à l'opération de match et de la ranger parmi les autres fonctions. D'après le principe de la séparation des tâches, les fonctions appartiennent aux bibliothèques qui seront conçues indépendamment du langage. Mais alors, il faudra donner aux fonctions des préfixes uniques. Et on aboutira à une interface du type :

       pcre *pcre_compile(const char *pattern, int options,
            const char **errptr, int *erroffset,
            const unsigned char *tableptr);

Je ne critique pas ici l'excellente bibliothèque pcre mais montre qu'un langage gagne à être un tout organique. Avec le langage elisp, on verra un autre exemple d'un match "désintégré" dans le contexte de l'explication du chunking.

On voit que, au nom même du minimalisme, de la régularité, et de l'orthogonalité (comme disent les mathématiciens), on arrive à des complications en pratique.

problème d'œuf et de poule

Il y a néanmoins un problème d'œuf et de poule. Il est difficile pour le concepteur d'un langage de savoir quelles en seront les constructions les plus utilisées. Et il y a un effet d'entrainement : un langage qui rend facile et puissante l'expression d'une fonctionalité verra celle-ci utilisée de manière intensive comme les regex en Perl.

Si le langage est conçu pour etre extensible au fur et à mesure des versions de l'interpréteur comme Perl, la fonctionnalité est enrichie jusqu'à devenir complètement baroque. Une analyse est alors nécessaire pour voir dans cette fonctionnalité quels sont les éléments constituants les plus fréquents. On verra plus bas à propos de la discussion de Perl1 que Perl a déjà fait celà dans sa syntaxe des regex comparée à celle de sed. Plus généralement, L'apocalypse 5 qui décrit les regex en Perl6, englobées dans le terme plus général de règles, montre un tel travail pour le passage à Perl6.

Mais finalement, le concepteur du langage reconnait qu'il n'est pas omniscient, qu'il ne peut pas prévoir quelles fonctionalités seront les plus utilisées. Il permet alors au langage d'être modifiable par le programme. Au sein de celui-ci, le programmeur pourra concevoir l'encodage syntaxique approprié pour une construction fréquemment utilisée. Je parle ici d'un mécanisme plus propre que celui des macro-expansions. L'apocalypse 5 parce de celà aussi.

Le chunking

Un des principes gourvernant la conception de la syntaxe de Perl est le chunking (découpage en morceaux). Les mécanismes humains de perception sont limités et fonctionnent grâce à des systèmes qui découpent la réalité en morceaux. On ne peut traiter à la fois qu'un nombre limité de morceaux. De plus, il faut pouvoir les identifier. La syntaxe d'un langage informatique doit être conçue pour permettre d'identifier facilement les constituants d'un programme. Autant que possible, chaque constituant doit apparaître comme un tout distinctif.

Heureusement, l'être humain est très adaptable et s'habitue à la syntaxe de son langage favori. Mais cela rend d'autant plus difficile une critique comparée des syntaxes puisqu'un langage familier apparaîtra toujours plus lisible qu'un langage à la syntaxe nouvelle.

Trop de morceaux similaires cache la structure

La tentation est de bien marquer les frontières comme en Lisp avec des parenthèses. Voici ci-dessous un exemple extrait des sources d'un module emacs : gud.el. Le minimalisme de Emacs fait que tout est une s-expr. Ainsi, il est difficile pour l'œil de discerner le contrôle de flot, puisqu'il n'y a pas de syntaxe particulière pour les blocs et les prédicats qui les commandent. Tout est ravalé au rang d'appel de fonctions. L'auteur est obligé d'utiliser abondamment les indentations et les retours à la ligne pour rendre son code lisible. Encore une fois, il n'y pas de convention syntaxique particulière pour les regex et les match. Ainsi, (substring result (match-beginning 2) (match-end 2)) s'exprimerait simplement $2 en Perl ! Ce dégueulis de parenthèses propre à Lisp, Larry l'appelle des « rognures d'ongles » (clipped nails).

	  (let ((file (gud-dbx-file-name
		       (substring result (match-beginning 2) (match-end 2)))))
	    (if (and file (file-exists-p file))
		(setq gud-last-frame
		      (cons
		       file
		       (string-to-int
			(substring
			 result (match-beginning 1) (match-end 1)))))))
	  (setq result (substring result 0 (match-beginning 0))))))
    (or result "")))

La classe de langages ML (SML, CAML light, OCAML...), outre le minimalisme syntaxique, pêche par un défaut opposé à Lisp, la quasi-absence de délimiteurs visuels. Il n'y a donc pas de marques distinctives qui permettent de guider l'œil pour identifier la nature et les frontières d'un constituant syntaxique. Le mot-clé let est utilisé à la fois pour définir et nommer les fonctions et les variables.

Les maths comme déconstruction

Plus gênant pour le débutant en OCAML est le déchunking dans l'expression des signatures de fonctions. Mathématiquement, une fonction via l'opération de currying peut être une fonction d'un paramètre qui peut être elle-mêmme une fonction. Ci-dessous, on utilise l'évalueur interactif pour définir les fonctions add d'addition d'entiers et inc d'incrément par une valeur. On voit qu'elles sont équivalentes pour le système de types d'OCAML.

  # let add a b = a + b  ;;
  val add : int -> int -> int = <fun>
  # let inc a = function b  -> a + b ;;
  val inc : int -> int -> int = <fun>

La surconstruction

On peut voir comme en Java, un programme composé d'une instruction comme une classe qui ne comprend qu'une méthode. A quoi sert ici, dans le cadre d'un programme élémentaire, tout l'appareillage qui sert à calibrer et composer les éléments d'un programme complexe ?

  # "Salut les mongueurs" en Java
  class Hello
  {  
	  public static void main(String args[])
	  {
	     System.out.println("Salut les mongueurs !");
	  }
  }

  # "Salut les mongueurs" en Perl
  print "Salut les mongueurs !\n";

L'évolution de Perl

Perl a été à l'origine conçu comme un awk amélioré. Il est devenu un langage beaucoup plus puissant sans sacrifier la compatibilité avec ses versions les plus anciennes.

Les langues naturelles évoluent naturellement vers la simplification et la concision. C'est vrai dans la limite imposée par les académies qui tendent à considérer l'évolution naturelle des langues comme une dégénérescence.

Mais les langages informatiques ont plus de difficulté à se libérer des bagages inutiles accumulés en cours de route sauf à évoluer de manière incompatible. Mais c'est perdre l'avantage acquis des bibliothèques écrites pour ce langage. Ce pas difficile et nécessaire sera franchi par Perl6

Il supporte maintenant l'écriture de programmes complexes mais au prix d'une syntaxe souvent malcommode. Pour les amateurs de linguistique diachronique, il est passionnant de voir l'évolution du langage Perl de Perl1 au futur Perl6.

Le concepteur d'un langage se trouve face à un dilemme. Doit-il commencer par un petit langage qu'il va étendre ou concevoir immédiatement un gros langage ? Larry Wall a choisi la première solution. On va voir à travers l'introduction et l'évolution de quelque fonctionalités comment le langage évolue sans sacrifier la compatibilité. Le critère de Huffman et le chunking seront nos critères d'analyse. On voit les choix dictés par ces critères évoluer avec les types de programmes écrits en Perl.

J'ai donné les dates d'introduction des versions du langage. On voit le langage évoluer rapidement pour atteindre une maturité avec Perl5.

perl1 -- 18 décembre 87

Dans les regex, les métacaractères, comme les parenthèses pour la capture, sont très courants. Donc contrairement à l'usage établi par sed, ils ne sont pas précédés d'antislash. Le principe de Huffman est déjà à l'œuvre.

Les programmes étant courts, les appels de routines (définies par l'utilisateur) sont rares par rapport à ceux de fonctions (prédéfinies). Routines et fonctions vivent dans deux espaces de noms différents et sont invoquées via une syntaxe différente, ce qui permettra de pouvoir rajouter plus tard des fonctions au langage sans risque de conflit avec des noms de routines.

Puisque plus rare, l'invocation des routines est plus verbeuse que celle des fonctions. Appel de routine : do routine ( liste ). Appel de fonction : function( liste ).

Perl1 introduit la convention lexicale des sigils pour définir et accéder à trois types de données : les scalaires, les tableaux et les hash. Cette distinction entre tableau et hash est originale. La plupart des langages de script comme awk ou javascript utilisent la syntaxe C d'accès aux tableaux, mais pour accéder aux hash. Dans ces langages, les tableaux ne sont que des hash dont les clés sont des chaînes représentant des entiers.

Ces langages étant interprétés, la différence de performance entre véritables tableaux et hash utilisés comme des tableaux est minime. Néanmoins, tableau et hash sont des concepts différents et méritent d'être distingués. De plus avec Perl6, qui pourra être compilé, la différence de performance ne sera plus négligeable.

Perl1 se pose en remplacement de sed et de awk et propose des convertisseurs de ces langages vers Perl. Le convertisseur de sed en perl, s2p, est écrit en Perl et fait déjà 552 lignes. La syntaxe de awk nécessite un vrai parser ; a2p, le convertisseur de awk en perl est donc écrit en C et comporte un parser. L'option -n permet à Perl de se comporter comme awk par l'emballage du programme dans une boucle.

Perl2 -- 5 Janvier 1988

Dès la version 2, Larry satisfait le besoin d'avoir des portées pour les variables mais les variables sont locales et non lexicales. L'opération de substitution pour créer une nouvelle variable est jugée tellement courante qu'elle est supportée par la syntaxe :

   ($obj = $src) =~ s/\.c$/.o/;

Perl3 -- 18 octobre 1989

Les fonctions pack et unpack permettent de manipuler des structures de données binaires. La variable $; permet de simuler des tableaux multidimensionnels.

Perl4 -- 21 mars 1991

Perl4 n'apporte rien de fondamentalement nouveau à Perl3. Sa sortie est conjointe avec celle de la première édition de Programming Perl chez O'Reilly.

Perl5 -- 12 mars 1995

Le langage supporte enfin des structures de données composites et l'objet. Les comportements par défaut deviennent malheureusement inadéquats.

La liste des fonctions prédéfinies étant jugée stable, ces fonctions et les routines peuvent partager le même espace de nom et la même syntaxe d'appel.

Dans un langage avec des structures de données complexes, il est nauturel de passer par défaut les valeurs composites comme les tableaux ou hash par référence. Par compatibilité, ce n'est pas le cas.

  appel_fun( @a, %a );    # défaut malheureux expanse @a et %a 
  appel_fun( \@a, \%a );  # la compatibilité impose un syntaxe lourde 
                          # pour le passage de paramètre par référence

Le futur Perl6

Au fur de l'évolution de Perl les choix des comportements par défaut se sont révélés inadaptés pour le support des gros programmes. Pire, beaucoup de programmeurs n'utilisent pas les fonctionalités indispensables à l'écriture de gros programmes comme les strictures proposées par strict.pm. Perl6 utilisera le contexte pour trouver le comportement par défaut qui sera le plus adapté. Les gros programmes utilisent des bibliothèques qui sont chargées par la fonction use. Ce sera le critère pour choisir le mode strict.

Les noms de variables "magiques" étant par trop cryptiques, Perl5 avait fini par proposer le module English et documenter ces nouveaux noms directement dans perlvar(1). Perl6 supportera par défaut des noms explicites.

Le système de regex devient suffisamment puissant pour devenir un véritable parser. Un tel parser pourra avoir des actions sémantiques qui agirons sur l'arbre abstrait représentant le programme. En d'autre terme, le programme pourra faire évoluer le langage à loisir.

Apocalypses, Exégèses et Synopsis

Mais Perl6 sera beaucoup plus qu'une « remise au propre ». Les apocalypses [5], documents au sens de révélation, expliquent les limitations de Perl5. Elles justifient les choix opérés lors de la conception de Perl6. Elles montrent comme le souci d'une syntaxe lisible et efficace est un souci permanent pour Larry Wall. Les synopsis [6] sont les documents de référence de conception de Perl6. L'objet des apocalypses et des synopsis [7] n'est pas d'être pédagogiques. C'est le rôle des exégèses qui montrent Perl6 en situation avec des exemples.

Le processus de création de Perl6

Larry et la communauté Perl prennent leur temps pour créer Perl6. « Better Right than right now. » (plutôt bien que dès maintenant) a dit Larry. Le processus du création de Perl est mal compris même à l'intérieur de la communauté Perl. Ça n'est pas un plan quinquennal rigide mais un effort qui accepte l'entropie comme partie intégrante du processus créatif.

Larry veut créer un langage qui pourra évoluer autant que l'a fait Perl1 jusqu'à Perl5. Mais la tâche est plus ambitieuse, puisque le langage initial sera déjà très puissant.

La décision de créer un langage qui gardera la philosophie de Perl6 a été prise lors de la conférence OSCON durant l'été 2000. Après une phase tumultueuse de consultation de la communauté, Larry a créé une équipe resserrée pour la conception de Perl6. Larry et cette équipe ont mis plusieurs années pour créer progressivement l'ensemble des spécifications dont nous avons parlé : les apocalypses. Cet effort a été en partie validé par l'écriture de modules Perl6 généralement en terme de filtre source Perl5 tels que Perl6::Rules ou Perl6::Parameters. Très rapidement, en septembre 2001 il y avait suffisamment d'éléments pour commencer à travailler à Parrot, la machine virtuelle qui sera le moteur de Perl. Très récemment, en février un autre effort, Pugs, écrit en Haskell a permis de créer un interpréteur Perl6 à fin d'expérimentation. A l'heure où j'écris, au tout début d'Avril, cet effort vient de se coordonner avec Parrot : Pugs émet maintendant du code Parrot.

Pugs permettra probablement de bootstrapper le compilateur Perl6 écrit en Perl6. Cela a réveillé la communauté Perl qui commence déjà à convertir des modules Perl5 en Perl6. Tous ces efforts sont discutés sur des listes courriels publiques [8] .

Il est difficile de savoir quand Perl6 aura la maturité pour progressivement supplanter Perl5. Probablement des applications apparaîtront qui utiliseront la rapidité de la machine virtuelle Parrot et de son JIT avec l'exploitation de fonctionalités propres à Perl6. Ce sera un signal et un modèle pour les autres programmeurs.

Mais l'effort Perl6 n'empêche pas l'évolution et le support de différentes versions de Perl5. Rafaël Garcia Suarez, un mongueur de Mandrakesoft, est au cœur de ces efforts. Je vous reporte à sa présentation au FOSDEM [9].

Références

À 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]