Métaprogrammation
La métaprogrammation, nommée par analogie avec les métadonnées et les métaclasses[réf. souhaitée], désigne l'écriture de programmes qui manipulent des données décrivant elles-mêmes des programmes. Dans le cas particulier où le programme manipule ses propres instructions pendant son exécution, on parle de programme auto-modifiant.
Elle peut être employée pour générer du code interprété par un compilateur et donner un résultat constant, afin d'éviter un calcul manuel. Il permet également de réduire le temps d'exécution du programme si le résultat constant avait été classiquement calculé par le programme comme pour les résultats variables.
Cette méthode ne s'applique pas uniquement aux calculs mais aussi au remplissage de données constantes telles que des tableaux ou des structures plus complexes. Cependant cette technique ne fonctionne que pour des valeurs constantes. En effet, si une donnée manipulée par le métaprogramme est une entrée du programme, par exemple une saisie de l’utilisateur, elle ne peut pas être connue avant l'exécution du programme. Il est donc impossible qu'un tel métaprogramme soit interprété par un compilateur. L'optimisation par métaprogrammation est alors totalement perdue.
La métaprogrammation ne se limite pas seulement à l'écriture de données contenant un programme destiné au compilateur. Elle peut simplement être la manipulation d'un programme en fonction d'entrées variables. Par exemple, un programme peut, selon ses entrées, muter le code d'un métaprogramme. Ce métaprogramme peut alors être destiné à une exécution ultérieure ou une génération de code.
Processus
Il existe différentes façons de procéder :
- l'utilisation de générateur de code,
- l'utilisation de la généricité et des modèles (déclarations génériques avec Ada, templates avec C++ ou Common Lisp),
- l'utilisation d'un protocole à méta-objets (en anglais : MOP) comme le MOP de CLOS ou Smalltalk,
- l'utilisation de macros.
Les deux premières techniques sont disponibles pour les langages à typage statique. Il s'agit d'une forme puissante mais limitée de méta-programmation. Le principe du générateur de code revient en effet à construire un compilateur comprenant la sémantique d'un langage donné, avec des ajouts. Cette approche n'est donc pas facilement portable. La programmation à base de templates permet de construire des opérateurs pour des types de données complètement hétérogènes — c'est utile en C++. Les templates de Common Lisp sont plus généraux. Ces deux techniques ne concernent que la phase de compilation. Certains langages académiques (comme MetaOcaml par exemple) fournissent un typage qui garantit que les programmes générés par le méta-programme sont correctement typés.
Les langages réflexifs offrent des moyens d'introspection et de modification en cours d'exécution, non seulement des valeurs et objets du domaine d'une application mais du comportement du système (entendre comme le langage + ses bibliothèques standards). Les protocoles à méta-objets permettent de spécifier le comportement au niveau des classes elles-mêmes (on y considère les classes, les méthodes, comme des objets d'un domaine particulier).
Un système de macro-définition (ou macros) permet de réaliser des transformations de source à source : on peut ainsi ajouter de nouveaux opérateurs à un langage sans altérer sa spécification ni modifier le compilateur (contrairement au principe des générateurs de code). Seuls les langages représentés avec des s-expressions offrent un système de macros satisfaisant et utilisable, du fait de la convergence entre la syntaxe abstraite des programmes et leur syntaxe concrète.
Certains systèmes experts explicitent ou dirigent le fonctionnement de leur moteur d'inférence par des méta-règles ou méta-connaissances qui peuvent être considérées comme des méta-programmes.
Exemple
Par exemple, pour effectuer le calcul d'une somme finie des nombres entiers inférieurs à 10 (ou toute autre valeur constante), il faut faire le calcul 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10, on peut :
- créer une fonction qui calcule la somme de i allant de 1 à n, puis donner n = 10 en paramètre ;
- calculer à la main pour utiliser directement le résultat : 55 ;
- faire un métaprogramme qui calculera la somme lors de la compilation.
Dans le premier cas, on crée une fonction qui permettra au programme de faire le calcul. Cette technique peut paraître inutile et faire perdre du temps à l'exécution du programme car tout est constant. Le programme serait plus optimisé si le calcul était déjà fait avant son exécution.
Dans le deuxième cas, on calcule soi-même la valeur et on la met dans le code source du programme. Ceci pose deux inconvénients :
- Le calcul peut être faux. Ceci causerait ainsi des erreurs dans les résultats fournis par le programme. De plus, il est difficile de déboguer et retrouver l'origine du problème ;
- Le calcul peut être long et fastidieux. En effet, sur l'exemple, seulement 10 valeurs sont calculées, mais une plus grande somme prendrait beaucoup plus de temps. Même si, sur cet exemple, on peut démontrer que (suite arithmétique), il n'existe pas toujours une formule mathématique simplificatrice, ou alors on n'en connaît pas.
Dans le troisième cas, le calcul est effectué en programmant un programme destiné au compilateur. Celui-ci exécute ce métaprogramme pour le transformer en donnée constante. C'est la méthode la plus optimisée pour les calculateurs humains et informatiques, car le métaprogramme n'est qu'une donnée qui, dans ce cas, est évaluée sans exécuter le programme final.
Voir aussi
Articles connexes
- Métaprogrammation avec des patrons, un exemple de programmation à la compilation en C++
- Portail de l’informatique