Métaprogrammation et
libre disponibilité des sources

deux défis informatiques d’aujourd’hui*

François-René Rideau
francoisrene.rideau@cnet.francetelecom.fr
http://fare.tunes.org

CNET DTL/ASR (France Telecom) #
38–40 rue du general Leclerc
92794 Issy Moulineaux Cedex 9, FRANCE

Résumé:

Nous présentons de façon complètement informelle la métaprogrammation, dont nous esquissons une théorie. Nous expliquons en quoi elle représente un enjeu majeur pour l’informatique d’aujourd’hui, dès lors que l’on examine les processus sous-jacents au développement logiciel. Nous montrons par les mêmes considérations, en quoi la métaprogrammation est liée à un autre défi de l’informatique, la libre disponibilité des sources des logiciels, et comment ces deux phénomènes se complètent naturellement.

1   Introduction

Dans les conditions primitives des débuts de l’informatique, la capacité des machines était si petite qu’un seul homme pouvait embrasser de la pensée l’ensemble du fonctionnement d’un programme jusqu’à ses moindres détails. Cependant, les facultés humaines n’ont pas changé depuis, tandis que la capacité des machines a suivi une progression géométrique soutenue. Pour profiter de l’évolution technologique, il a donc été nécessaire de développer et d’utiliser des outils conceptuels et des langages de programmation de plus en plus abstraits.

Mais quels que soient les progrès effectués individuellement par les programmeurs, il est un niveau de complexité depuis fort longtemps dépassé au-delà duquel nul ne peut seul concevoir dans son intégralité un programme qui tire pleinement parti des machines existantes. Il est donc primordial de développer des méthodes d’échange, de coopération, d’accumulation, de construction, pour permettre l’élaboration de programmes complexes ; tel est le domaine du génie logiciel [Brooks1995].

Or, au centre de tout processus de construction logicielle, il y a la manipulation des sources des programmes. Améliorer ces processus, faciliter la tâche du programmeur, c’est délivrer le programmeur de toutes les opérations répétitives et conceptuellement redondantes, pour qu’il puisse se concentrer sur l’essentiel de la programmation, c’est-à-dire sur les problèmes qui n’ont encore jamais été résolus. C’est donc automatiser autant que possible la manipulation du code, en faisant exécuter de manière fiable par la machine toutes les tâches subalternes qui ne posent plus de problème théorique. Et automatiser la programmation, c’est par définition métaprogrammer.

La métaprogrammation, art de programmer des programmes qui lisent, manipulent, ou écrivent d’autres programmes, apparaît donc naturellement dans la chaîne de développement logiciel, où elle joue un rôle essentiel, ne fusse «que» sous la forme de compilateurs, interpréteurs, débogueurs. Cependant, elle n’est quasi jamais intégrée consciemment dans les processus de développement, et la prise de conscience de son rôle est précisément le premier pas vers un progrès en la matière.

Maintenant, même si nous voulions nous consacrer à la tâche technique, suffisamment ardue en elle-même, consistant à explorer les méthodes d’automatisation du processus de développement logiciel, il nous est impossible d’ignorer la précondition nécessaire à l’utilisation de toute telle méthode ainsi que de tout travail incrémental ou coopératif : la disponibilité des sources.

Cette disponibilité est moins que jamais un problème technique, grâce à l’avènement des télécommunications numériques ; mais elle est plus que jamais un problème politique, à l’heure où la quasi-totalité de l’information sur la planète est sous le contrôle de monopoles éditoriaux organisés en puissants groupes de pression. C’est pour la libre disponibilité des sources, voire de l’information en général, que milite le mouvement pour le Libre Logiciel, qui combat les barrières artificielles que sont les privilèges légaux de «propriété intellectuelle».

Métaprogrammation et libre disponibilité des sources, tels sont les deux défis majeurs, intimement liés, auxquels a à faire face l’informatique aujourd’hui, et qui acquièrent une importance chaque jour plus grande. Tous deux trouvent la même justification cybernétique1 dans la nécessité d’adapter le processus de développement logiciel à des programmes de plus en plus élaborés concernant un public de plus en plus étendu, où chacun ne peut apporter qu’une petite pierre. Telle est du moins notre conviction, que nous allons tenter de vous faire partager.

2  Métaprogrammation

2.1  la métaprogrammation dans le quotidien

Il est possible de voir l’impact de la métaprogrammation sur les processus traditionnels de développement, en considérant les programmes en tant que processus transformationnels (boîtes transformant entrées en sorties) et en examinant si parmi leurs entrées ou sorties ils comptent des objets eux-mêmes catégorisables comme programmes.

La plupart des applications, destinées à un usage final par des non-informaticiens (logiciels de gestion et facturation, jeux) voire dans un milieu sans interaction humaine (contrôle embarqué), n’exposent pas parmi leurs entrées et sorties de programmes. Ce sont des programmes “de base”, non-méta.

Des métaprogrammes apparaissent cependant immédiatement durant le développement de ces applications: en effet, de nos jours, plus personne n’écrit directement de programmes en binaires sur un tableau de commande ou des carte perforées, et tout programme, entré par l’opérateur sous forme de code lisible par l’humain, doit être transformé avant d’être exécuté par la machine. C’est là le rôle des compilateurs et autres assembleurs, métaprogrammes par excellence, qui prennent en entrée un programme “source”, et le transforment statiquement (c’est-à-dire avant son exécution effective) en un programme “objet” de comportement équivalent, mais dans un langage plus directement exécutable par la machine. Une autre catégorie de métaprogrammes, complémentaire de celle des compilateurs, est celle des interprètes, qui exécutent dynamiquement un programme écrit dans un langage donné: un interprète efficace contiendra un compilateur qui transformera avant de les exécuter les programmes en une forme plus apte à l’exécution; à l’opposé, un compilateur plus soucieux de portabilité que d’extrême performance produira un code objet écrit dans un langage intermédiaire lui-même interprété par un interprète simple, plutôt que du code directement exécutable par la machine (qui dépend trop du type de machine en question).

Mais la métaprogrammation ne s’arrête pas au seul support d’exécution des programmes.

Pour mieux comprendre et contrôler le fonctionnement des programmes et éviter des erreurs au coût prohibitif, on fait appel à des métaprogrammes. Les plus courants sont les analyseurs statiques et testeurs dynamiques, qui permettent de détecter et d’éliminer des programmes de larges catégories d’erreurs. Profileurs, débogueurs et autres systèmes interactifs ou post-mortem culminant avec les explicateurs de systèmes experts, permettent à l’opérateur de comprendre le comportement dynamique des programmes.

Pour améliorer la performance des programmes, on trouve de nombreux optimiseurs et évaluateurs partiels; on trouve aussi des compilateurs de données qui transforment statiquement les données d’un problème particulier en un programme capable de résoudre le problème efficacement voire des précompilateurs de programmes, qui transforment un programme en un compilateur de données adapté à une classe de problèmes (utilisés notamment pour obtenir des routines de calcul numérique les plus rapides possibles2.

On peut aller plus loin et modifier le sens de programmes existants, pour pouvoir étendre leurs fonctionnalités ou gérer automatiquement certains de leurs aspects. Ainsi, un arpenteur de code permet-il de parcourir un programme pour effectuer automatiquement des transformations décrites par un métaprogramme; une application en est l’instrumentation de programmes (insertion de code), pour détecter les erreurs, gérer les performances, ajouter de façon transparente la gestion de la persistance des données ou de la répartition des calculs. Un tresseur d’aspect parcourra plusieurs programmes en même temps, pour produire un unique programme dont les précédents seront autant de facettes, tandis qu’au contraire un séparateur de phase transformera un unique programme en multiples parties chacune pouvant être exécutée au bon moment dans le contexte logiciel et matériel approprié.

Finalement, on peut transformer complètement des programmes pour obtenir d’autres programmes ayant des propriétés en rapport avec le programme de départ: dériver ou intégrer formellement des fonctions exprimés algorithmiquement, rechercher les solutions d’un problème décrit par un programme, extraire automatiquement des interfaces homme-machine à partir de descriptions de structures de données, etc.

On pourrait continuer cette énumération d’usages de la métaprogrammation. En regardant la liste d’exemples ci-dessus, on ne peut que constater qu’il existe dans chacune des catégories de logiciels cités qui sont effectivement utilisés quotidiennement, à plus ou moins grande échelle, par des programmeurs du monde entier. La métaprogrammation existe donc déjà, et fait preuve chaque jour de son utilité.

Cependant, par l’inspection de la même liste, on constate aussi que la plupart de ces métaprogrammes sont écrits de manière ad-hoc, et a posteriori, au gré des besoins immédiats, sans aucune infrastructure générale pour les développer (quoiqu’avec une collection d’outils décousue), et pis encore, sans aucun cadre pour les penser. Certes, la métaprogrammation est présente, mais rares sont ceux qui en ont conscience, et plus rares encore ceux qui l’utilisent activement [Pitrat1990] ; la tradition informatique semble plus encline à créer un cas particulier pour chacun des concepts présentés qu’à les rapprocher par une théorie utile et systématique.

2.2  esquisse d’une théorie de la métaprogrammation.

Il n’est pas particulièrement compliqué de formaliser l’idée de métaprogrammation, une fois qu’on s’y attache ; en effet, toutes les idées sous-jacentes existent déjà, et n’attendent que d’être collationnées en un tout cohérent.

La métaprogrammation consiste en la transformation de programmes. Sa théorie commence donc par la théorie de ce qu’est un langage de programmation. Pour résumer les points essentiels d’une théorie par ailleurs largement étudiée, un langage est donné par une syntaxe et une sémantique: sa syntaxe est la donnée d’un type récursif de données, décrivant l’ensemble des programmes sources valides du langage; sa sémantique associe à chaque programme source son comportement observable en tant que programme exécutable.

Un programme est donc toujours considéré dans le contexte d’un langage de programmation donné, et considéré à équivalence de comportement près: deux programmes sont égaux s’ils se comportent de la même façon pour l’utilisateur. Notons qu’à un “même” langage de programmation peut correspondre de nombreux niveaux de précision ou d’abstraction dans la description du comportement; deux programmes identiques à un niveau peuvent s’avérer différents à un autre. Jongler entre langages et niveaux de langages est la tâche dévolue à la métaprogrammation.

La métaprogrammation consistera donc à établir des correspondances entre langages, et plus précisément à construire ces correspondances elles-mêmes comme des programmes. Un métaprogramme sera donc un programme écrit dans un langage (un “métalangage”), décrivant une correspondance entre programmes d’une famille de langages sources et programmes d’une famille de langages cibles. Les métaprogrammes étant eux-mêmes programmes d’un métalangage, peut alors s’appliquer à eux des métamétaprogrammes écrits dans un métamétalangage (des exemples typiques étant des outils de construction de compilateurs, tels que générateurs d’analyseurs syntaxiques).

On pourra dire de certains métaprogrammes qu’ils traduisent fidèlement un langage dans un autre s’ils préservent la sémantique des termes à un certain niveau d’abstraction. Pour faire de la métaprogrammation correcte, il est important de savoir quel programme ou métaprogramme est considéré dans quel contexte sémantique. Ainsi, un compilateur transforme un programme du langage source en programme du langage cible, on conservant le comportement observable par l’utilisateur, et en minimisant l’utilisation de ressources (comportement observable à un niveau plus fin). De tels critères de correction peuvent être sujets de vérifications automatiques par des métamétaprogrammes appropriés; la métaprogrammation peut en effet servir non seulement à écrire et transformer des programmes, mais aussi à raisonner sur des programmes.

Finalement, quand une infrastructure de métaprogrammation est capable de se représenter elle-même, elle est dite réflexive3. Cette propriété est souvent obtenue en corollaire d’une propriété plus générale d’universalité permettant à l’infrastructure de représenter toute autre infrastructure de programmation. Une infrastructure réflexive permet de ne pas avoir à introduire un nouveau métalangage (et tous les axiomes associés) à chaque fois que l’on veut approfondir le comportement du système dans la direction «méta» ; elle efface aussi la distinction entre programmes et données, et conduit à une vision plus intégrée du développement logiciel.

2.3  programmation multi-aspects

Si nous examinons les applications existantes de la métaprogrammation, nous nous rendons compte qu’elle sert à gérer automatiquement la transition entre plusieurs aspects de mêmes objets informatiques : ainsi, par exemple, quand on compile un programme, on s’intéresse au «même» programme, sous différentes formes (code source ou objet), chacune adaptée à l’effectuation d’opérations différentes (modification par l’homme, ou exécution par une machine).

Conceptuellement, on considère un «même» programme, un «même» objet, une «même» idée, indépendamment des diverses représentations par le truchement desquelles on les communique ou les manipule ; une forme aussi bien qu’une autre peut servir à en décrire l’ensemble des propriétés «intéressantes». Cependant, quelle que soit la représentation choisie, il faut bien en choisir une, et compte tenu des critères d’effectivité imposés par les contraintes physiques et économiques, il vaut mieux en choisir une «adaptée» à réaliser aussi efficacement que possible les manipulations que l’on envisage sur l’objet auquel on s’intéresse.

Or, il est inévitable que les propriétés «intéressantes» d’un objet varient dans l’espace et dans le temps, selon les personnes qui considèrent l’objet, et les composants machines qui le manipulent, selon le domaine d’activité, les attentes, et les compétences des uns, selon le but d’utilisation et les contraintes techniques des autres. Si l’on veut présenter à chaque programme de chaque utilisateur et à chaque moment une représentation des objets considérés la plus adaptée possible à ses intérêts momentanés, il est besoin de métaprogrammes pour assurer la cohérence dans le temps et dans l’espace entre les divers aspects ce ces objets.

Ainsi, un avion sera pour un ingénieur s’occupant du fuselage, un ensemble de courbes et d’équations dont il faut optimiser certains paramètres, pour le fabricant de pièces, ce sera un cahier des charges, pour le constructeur, ce sera un processus d’assemblage, pour le responsable de l’entretien, un ensemble de tâches à effectuer, pour le réparateur, un ensemble de problèmes à régler, pour le gestionnaire matériel, un ensemble de pièces détachées, pour le pilote, un appareil à mener à bon port, pour le passager, un inconfort à minimiser pour parvenir à destination, pour le contrôleur aérien, un point à router parmi d’autres, pour l’agent commercial, un ensemble de sièges à remplir, pour le stratège commercial, l’élément d’une flotte à déployer, pour le responsable du personnel, des humains à gérer, pour l’agent comptable, un historique de transactions, pour l’assureur, un risque à évaluer, etc., etc. L’informatisation de tout ou partie du suivi de la vie d’un avion implique de présenter à chacun des acteurs en présence un point de vue de l’avion adapté à ses besoins, et qui contient des paramètres dont la plupart ne sont utiles qu’à lui, mais dont certains concernent aussi d’autres acteurs, avec qui il est essentiel de se mettre d’accord, de synchroniser les données. La métaprogrammation permet d’apporter cette synchronisation, cette cohérence entre les points de vue des multiples acteurs [Kiczales et al.1997].

Même pour des projets informatiques plus simples que la gestion de A à Z d’une flotte aérienne, il apparaît nécessairement des divergences de points de vue, dès lors que plusieurs acteurs (programmeurs, utilisateurs) sont en présence, et/ou que ceux-ci évoluent, et changent de centres d’intérêt au cours du temps (la même personne pouvant tenir successivement plusieurs rôles). Dès qu’un problème est suffisamment complexe, nul n’a de toute façon les ressources nécessaires pour en embrasser d’un coup l’ensemble de tous les aspects, et il est nécessaire de traiter ceux-ci séparément. Dès lors, la métaprogrammation est utile pour assurer la cohérence entre ces aspects, qui devront sans elle être gérés manuellement ; elle permet de s’occuper une fois pour toutes d’un aspect inintéressant, pour pouvoir l’oublier après.

3   Expressivité de la métaprogrammation

3.1   expressivité et calculabilité

Nous avons présenté la métaprogrammation comme une technique fort utile (et largement utilisée, sans qu’il y en ait conscience) pour résoudre certains problèmes. La question se pose alors de savoir en quoi cette technique est originale, ou ne serait qu’une combinaison (qu’il serait alors intéressant de détailler) de techniques existantes et déjà bien connues, en quoi elle apporte ou non des solutions nouvelles à des problèmes connus ou non encore résolus auparavant, si en fin de compte, c’est une technique essentielle et indispensable, ou si elle est au contraire accessoire.

Cette question est en fait celle de l’expressivité des langages de programmation, en tant qu’algèbres permettant de combiner de multiples techniques : qu’est-ce qui rend un langage plus expressif qu’un autre ? en quoi aide-t-il plus ou moins bien le programmeur à résoudre les problèmes qui sont les siens ? et d’abord, quels sont ces problèmes, et comment les caractériser et les comparer, eux et leurs solutions ?

Le résultat fondamental concernant l’expressivité des systèmes de calcul est celui de Turing [Turing1936], qui montre l’existence d’une classe de fonctions dites calculables, capables d’effectuer tout calcul mécaniquement concevable. Étant donné un ensemble d’entrées (questions) possibles et un ensemble de sorties (réponses) possibles, tous deux numérisables (encodables avec, par exemple, les entiers naturels), tout langage de programmation mécaniquement réalisable peut donc exprimer au plus autant de fonctions de l’ensemble des entrées dans l’ensemble des sorties qu’une machine de Turing. Un langage de programmation qui peut exprimer toutes les fonctions calculables d’un ensemble dans l’autre est appelé universel, ou Turing-équivalent (en supposant ces deux ensembles infinis). Les langages universels sont tous aussi puissants les uns que les autres, du point de vue des fonctions qu’ils peuvent exprimer.

Cependant, il est évident que tous les langages Turing-équivalent ne se valent pas du point de vue du programmeur : tous ceux qui en ont fait les expériences respectives conviennent qu’il est plus facile de programmer dans un langage de haut niveau (comme LISP) que dans un langage de bas niveau (comme C), qui est plus facile d’utilisation que le langage d’assemblage, qui vaut mieux que le code binaire, qui lui-même est plus simple que la fabrication transistor par transistor d’un circuit électronique dédié, ou la spécification d’une machine de Turing. En effet, le résultat de Turing ne constitue que le tout début d’une théorie de l’expressivité des systèmes de calcul, et certainement pas la fin. S’arrêter sur ce simple résultat, et dire «puisque tous les langages, dans la pratique, ne sont pas équivalents comme ils le sont selon Turing, c’est que la théorie n’a rien à dire», ce serait abdiquer la raison et chercher dans un ailleurs vague quelque explication ineffable, ce serait donner la primeur à l’ignorance et la superstition.

Avant d’aller plus loin, notons que la démonstration du résultat de Turing est toute entière fondée sur la métaprogrammation : si les langages universels sont équivalents les uns aux autres, c’est parce qu’il est toujours possible d’écrire dans n’importe lequel de ces langages un interpréteur pour tout autre langage, si bien que tout programme dans l’autre langage peut trouver, modulo l’usage d’un traducteur, un équivalent dans le langage de départ. C’est d’ailleurs pour cela que ces langages sont appelés universels. Or, la métaprogrammation est un style de programmation ignoré de toutes les méthodes de développement logiciel appliquées à la plupart des langages4, car elle consiste précisément à ne pas utiliser le langage étudié, mais un autre langage, via des métaprogrammes. Cette réticence vis-à-vis de la métaprogrammation peut-elle être formalisée, et que reste-t-il alors de l’équivalence entre langages universels ?

3.2   le processus compte

Il n’existe malheureusement pas encore de théorie pleinement satisfaisante de l’expressivité ; le seul travail récent que nous avons pu trouver sur le sujet [Felleisen1991], fort intéressant par ailleurs, limite son ambition à la macro-expressibilité relative d’extensions d’un même langage l’une dans l’autre. Cependant, nous disposons de quelques morceaux épars d’une théorie générale, qui suffisent amplement selon nous d’une part à justifier cette idée somme toute «intuitive» que la métaprogrammation apporte un surcroît d’expressivité, et d’autre part à raffiner ou rejeter d’aucunes affirmations communément entendues sur l’expressivité «excessive» de certains langages.

Le point d’achoppement sur lequel la notion de calculabilité ne suffit pas à exprimer(!) l’expressivité des langages de programmation, c’est que le développement de programmes informatiques ne se résume pas à l’écriture mono-bloc (ou l’échec d’une telle écriture) d’une solution parfaite à un problème statique donné, mais un processus dynamique et évolutif, faisant intervenir une interaction plus ou moins poussée entre l’homme et la machine.

Ainsi, pour tout programme donné à écrire sur une machine donnée, la solution véritablement optimale ne pourra s’exprimer qu’en langage binaire, et contiendra des tas de «trucs» de derrière les fagots, d’encodages basés sur des coïncidences heureuses, de «jeux de mots» sur l’encodage des données, des adresses, et des instructions machines. Mais le fait est que trouver une telle solution demanderait des forces titanesques, et l’apparition de nouvelles architectures matérielles rendront obsolètes toutes ces optimisations jeux-de-mots. Or les forces humaines dépensées au cours du développement sont importantes ; elles sont même essentielles, vu qu’au bout du compte, le but de l’ordinateur, comme de tout outil, est de minimiser la quantité d’efforts humains nécessaires à l’accomplissement de tâches de plus en plus élaborées. Si le coût relatif des ressources humaines et mécaniques était tel, dans les années 50, que la production de code binaire super-optimisé à la main était économiquement viable (voir l’histoire de Mel extraite du Jargon File [Raymond1996]) ; une telle production est impossible aujourd’hui.

Le problème n’est donc pas seulement technique, il est aussi économique, moral et politique, en ce qu’il concerne des déplacements d’efforts humains. Dans l’écriture de programmes informatiques, comme partout ailleurs, le processus compte. Et c’est bien ce processus qu’essaient d’améliorer sans le formaliser rationnellement les notions courues de réutilisation de code, de modularité, de programmation dynamique ou incrémentale, de méthode de développement, etc. De même, la tendance en informatique à abandonner les langages de trop bas niveau, en faveur de langages de plus haut niveau tient précisément à ce que l’intelligence humaine est une ressource limitée, voire rare, et qu’il faut la réserver pour les tâches les plus importantes, celles où elle est indispensable (sinon à jamais, du moins à cette heure).

Aussi, une modélisation satisfaisante de l’expressivité des langages de programmation, même assez abstraite pour ne pas dépendre excessivement de considérations technologiques éphémères, se doit de prendre en compte l’interaction homme-machine, et d’une façon qui inclue une notion de coût humain (et peut-être aussi des notions d’erreur et de confiance). Nous soupçonnons qu’une telle modélisation est possible en utilisant la théorie des jeux.

3.3   complexité de la programmation incrémentale

À défaut d’une théorisation algébrique cohérente de l’expressivité, nous pouvons en donner une première approximation intuitive suivante, prenant en compte une notion bête et méchante du coût humain : un système de programmation sera plus adapté à un certain usage qu’un autre, s’il nécessite «à la longue» moins d’interaction humaine pour résoudre successivement une série indéfinie de problèmes posés dans le domaine donné. On pourra utiliser les «métriques» approximatives courantes de production (nombre de lignes de code, ou de constructions syntaxiques abstraites, ou de symboles écrits, nombre de mois-hommes bloqués). Sans fournir de caractérisation fine de l’expressivité (qui serait nécessaire à la définition d’un style de programmation conséquent), cette approximation suffit à justifier l’usage de la métaprogrammation.

Dans le processus de développement traditionnel, d’où la métaprogrammation est excluse, la quasi-intégralité du code constituant les logiciels est écrite manuellement par l’humain ; le détail de l’exécution, l’ensemble du comportement, tout doit découler directement de la pensée humaine ; à chaque étape de développement, toute modification est l’œuvre de l’homme. Là où le bât blesse, c’est que tôt ou tard, certaines nouvelles fonctionnalités à ajouter à un projet logiciel nécessitent des changement architecturaux globaux plus ou moins profonds : toutes les modifications, globales, du programme devront alors être effectuées à la main, avec tous les problèmes d’incohérences involontaires introduites par de tels changements, qui induisent de nombreux bogues et coûtent de nombreuses itérations de développement (pour un tel phénomène publiquement documenté, voir les changements occasionnels d’API du noyau Linux ; pour un exemple spectaculaire, voir le bogue explosif de la première fusée d’Ariane V). L’alternative de ces modifications est la réimplémentation complète, qui coûte tout ce que coûte la réécriture de code. Finalement, le coût incrémental de développement traditionnel est proportionnel à la taille de code différent entre deux itérations du processus.

Faisons entrer la métaprogrammation dans le cycle de développement. La métaprogrammation permettant des traitements arbitraires du code par la machine, il devient possible au programmeur faisant face à un changement structurel de son programme de faire effectuer semi-automatiquement toute tâche d’instrumentation ou de transformation de son programme, pour le mettre en conformité avec la nouvelle architecture, pour vérifier le nouveau code vis-à-vis des invariants déclarés, pour assurer la cohérence des différentes parties de son programme. «Semi-automatiquement» signifie ici que le «métaprogramme» le plus simple pour gérer certains cas particuliers consiste parfois à les traiter manuellement au cas par cas. Bref, entre chaque itération du processus de développement logiciel, le coût incrémental est proportionnel non pas à la taille de code différent entre les états successifs du projet, mais à la taille du plus petit métaprogramme permettant de transformer l’état précédent du projet en l’état suivant5.

Dans le pire des cas, la métaprogrammation n’aura pas servi, et le coût sera exactement le même (à une constante additive négligeable près) que celui de la programmation traditionnelle6 ; la théorie nous enseigne que, par la définition même du concept d’aléatoire, cela correspond au cas où l’évolution du projet est complètement aléatoire par rapport à la base de code existante. Dans le cas le meilleur, un gain arbitraire a pu être fait. En général, la métaprogrammation permet un coût optimum du point de vue de la complexité ; ses gains par rapport à l’approche traditionnelle sont proportionnels à la taille du projet, dans le cas inévitable où des changements structurels à effets globaux sont nécessaires.

Ainsi, des considérations élémentaires de théorie de l’information nous permettent de voir que la métaprogrammation gagne toujours, et souvent de beaucoup, sur une programmation non méta, en optimisant le partage d’information entre itérations du processus de développement. On voit bien d’ailleurs combien les doses homéopathiques de métaprogrammation utilisée dans les projets logiciels traditionnels sont un facteur déterminant pour le déroulement de ces projets : choix, souvent statique, d’un environnement de développement ; filtrage des programmes au moyen d’outils d’analyse statique, vérificateurs d’invariants plus ou moins élaborés et autres lint ; usage d’éditeurs gérant l’indentation et la structure, qu’ils peuvent mettre en valeur avec fontes et couleurs ; utilisation occasionnelle d’outils de recherche et remplacement sur le source du programme ; dans le cas de bidouilleurs 7 plus audacieux, «macros» ou «scripts» écrits en elisp, perl ou autre, pour éditer et manipuler les sources.

Cependant, et ceci va se révéler très important pour la suite de notre exposé, la métaprogrammation gagne si et seulement si d’une part un effort infrastructurel est effectué pour permettre une manipulation aisée des programmes, et si d’autre part il y a une assez grande redondance entre développements successifs pour permettre un partage non trivial d’information au cours du temps ; le bénéfice permis par la métaprogrammation est donc asymptotique : il est faible au départ, mais se bonifie grandement avec la taille des programmes et le nombre d’itérations de développement, c’est-à-dire avec l’extension spatio-temporelle des projets de développement logiciel.

4  Disponibilité des sources

4.1   métaprogrammation contre propriété intellectuelle

Admettons l’intérêt des techniques liées à la métaprogrammation. Pourquoi ne sont-elles pas plus communément connues et consciemment utilisées ? Y aurait-il donc des freins qui ont empêché jusqu’ici leur plus large diffusion ?

Il nous semble quant à nous évident que les barrières à la diffusion des sources sont autant de freins au développement de la métaprogrammation. En effet, la condition même d’utilisation d’un métaprogramme lisant un programme en entrée est la disponibilité d’un programme à lire ; la condition d’utilité d’un métaprogramme écrivant un programme en sortie est que ledit programme puisse être diffusé et utilisé ; et ces conditions se combinent si le métaprogramme lit et écrit à la fois des programmes, et plus encore s’il dépend de l’accumulation sur le long terme de connaissances sur les programmes ! Toute limitation sur les manipulations de programmes limite d’autant la faisabilité ou l’intérêt des métaprogrammes, et décourage les métaprogrammeurs potentiels.

Or quelles sont les implications du régime actuel de «propriété intellectuelle» sur les programmes ?

D’abord il y a le droit dit «d’auteur», mais en fait, d’éditeur, puisque tout auteur abandonne tous ses droits à l’entreprise qui l’embauche ou qui l’édite (à moins de se faire lui-même éditeur ; mais il gagnera son argent non en tant qu’auteur mais en tant qu’éditeur, ce qui n’enlève rien au problème). Pour respecter ce «droit», un métaprogramme doit refuser de faire certaines inférences, certaines manipulations, qui violeraient (selon les pays) la licence sous laquelle le programme d’entrée est disponible. Quelles manipulations exactement sont interdites, la chose n’est pas claire.

Ainsi, certaines licences sont par utilisateur, ce qui interdit à un métaprogramme toute inférence qui soit utile à deux utilisateurs (ou à un utilisateur non autorisé) pour des notions d’utile et d’utilisateur restant à préciser (quid si deux personnes collaborent sur un même document ? ou si l’une porte assistance à l’autre ou lui sert de secrétaire ? quid si le résultat final du calcul est distribué à des millions d’individus ?). Si elles sont par machine, ou par utilisateur à la fois, le problème se complique, parce que les notions invoquées par ces licences deviennent inopérantes en présence de machines à temps partagé, de machines à multiprocesseurs, de machines en grappes, voire, pire, de machines plus rapides : aucun paramètre logiciel ne peut correspondre à ces notions, et encore moins avec des émulateurs logiciels d’architecture (émulateurs 680x0 ou x86), et pis encore si ces émulateurs font appel à des techniques d’analyse et de traduction de code (elles aussi prohibées par de nombreuses licences) qui dénaturent toute correspondance précise entre d’une part la machine «abstraite» pour laquelle le code est écrit et la licence prévue, et d’autre part la machine «concrète» sur laquelle tourne le programme. La notion d’utilisateur devient floue aussi face à des métaprogrammes qui utilisent automatiquement un programme donné, et le deviendra sans doute de plus en plus avec l’émergence souhaitée d’«intelligences artificielles». Tous ces problèmes peuvent être évités au prix énorme de n’utiliser en entrée des métaprogrammes que des logiciels et des informations libres de droits, ce qui, pour rester légaliste, requiert de faire signer un papier (ou cocher une case dans un menu ?) à toute personne introduisant au clavier quelque information originale.

Un problème plus épineux, que cette mesure ne résout pas, est la diffusion des résultats de métaprogrammes. Car, même avec des licences de logiciel libre, qui peuvent être incompatibles l’une avec l’autre, et plus encore avec des licences exclusives, certaines opérations sont permises, mais sous couvert de non-diffusion du résultat («usage privé»). Mais qu’est-ce exactement qui ne doit pas être transmis, quand un métaprogramme a inclus dans sa base de donnée persistante la synthèse des analyses de nombreux programmes ? Qu’est-ce que redistribuer ? Cela s’applique-t-il entre personnes d’une même entreprise ou entité morale ? Quid si cette entité est une association admettant toute personne comme membre ? Cela s’applique-t-il aux machines ? aux composants internes d’une même machine ? Et si cette même machine est une grappe en regroupant plusieurs ? Et si cette grappe recouvre le monde entier ?

Le fin du fin est l’existence de brevets. Ceux-ci affectent les métaprogrammes universellement, indépendamment du matériau de base utilisé et de leurs licences. Ils n’introduisent aucun règle structurelle interdisant une inférence logique dans un métaprogramme ; mais en revanche, si à un moment donné, le métaprogramme doit entraîner l’utilisation de certains algorithmes dans un but donné, alors l’inférence ayant entraîné ce résultat doit être rétroactivement invalidée, à moins que ne soit obtenue une licence auprès du détenteur de brevet. Mais comme les notions d’algorithme et de but sont on ne peut plus floues, il faudra un méta-méta-programme on ne peut plus élaboré, pour surveiller que le métaprogramme n’enfreint jamais aucun des millions de brevets déposés.

Si toutes les règles ci-dessus portant sur l’usage de métaprogrammes vous paraissent ridiculement absurdes, n’oubliez pas qu’après tout, les «intelligences artificielles», si elles diffèrent grandement dans leur qualité actuelle des «intelligences naturelles», sont basées sur les mêmes principes, et sur les mêmes contraintes. Ainsi, sachez que le métaprogramme le plus répandu actuellement, à savoir l’esprit humain, est le premier astreint à ces absurdes contraintes, par les mêmes absurdes lois !