Un ensemble d'expériences sur la métaprogrammation et la réfléxivité, réalisées dans différents langages dans le cadre de l'UE éponyme du premier semestre de master 2 AIGLE à l'Université Montpellier 2.
Langages utilisés :
- CLOS
- Java
- Ruby
- OpenJava
- Smalltalk
Autres langages possibles :
- Objective-C
- Python
- Javassist
- OpenC++
- Scala
Expérimentations réalisées :
- Design pattern singleton
- Classes qui mémorisent leurs instances (mémo-classes)
- Inspecteur d'objets
CLOS (Common Lisp Object System) est un ensemble d'opérateurs pour faire de la programmation orientée objets en Lisp. Ces opérateurs ne sont pas séparés du reste de Common Lisp mais historiquement on les regroupe tout de même.
CLOS et son MOP (MetaObject Protocol) sont un bon terrain pour faire de la métaprogrammation.
- ANSI Common Lisp, Paul Graham
En CLOS, la façon la plus simple de créer des singletons est de créer une méta-classe possédant un slot étant l'instance à mémoriser. Cette implémentation est d'ailleurs très répandue. Les classes singleton n'auront alors plus qu'à définir comme méta-classe la classe des classes qui n'ont qu'une seule instance.
Objectif : Définir la classe memo-class
des classes qui mémorisent leurs instances. Pour simplifier, on pourra lui associer la classe memo-object
des objets qui sont mémorisés par leur classe.
CLOS tire parti des méta-classes pour stocker les instances de chaque classe étant un mémo-object.
Un inspecteur d'objets tire parti du MOP pour afficher des informations sur chaque objet qui lui est donné et parcourir leur hiérarchie.
En Java, la métaprogrammation se fait par le biais de la librairie standard java.lang.reflect
. Java n'est pas un langage tourné vers la métaprogrammation, mais grâce à cette API il demeure possible de métaprogrammer.
Le design pattern Singleton est très commun en Java, et est par exemple utilisé pour java.lang.Runtime
ou java.awt.Desktop
. Si sa mise en place ne nécessite pas d'utiliser la métaprogrammation, il est intéressant de savoir comment l'en protéger : comment empêcher l'utilisation de l'API reflect pour créer deux instances d'un supposé singleton.
On considère donc deux approches : l'une, très simple, permise par les structures enum
) de Java > 1.5, et l'autre plus classique avec une initialisation tardive et un double verrouillage.
Tests :
Objectif : Définir la classe memo-class
des classes qui mémorisent leurs instances. Pour simplifier, on pourra lui associer la classe memo-object
des objets qui sont mémorisés par leur classe.
Affichage des propriétés d'un objet donné, séparant ses attributs hérités de ses attributs introduits, ses méthodes héritées de ses méthodes introduites.
Ruby est assez intéressant car il implémente nativement la notion de méta-classe (appelées en Ruby singleton classes ou eigenclasses).
- DrX, un inspecteur d'objet avec interface graphique
- Plus d'informations sur Wikipedia.
- Et encore plus d'informations.
- Ressources sur la métaprogrammation en Ruby.
Le pattern singleton peut être obtenu de nombreuses façons en Ruby. On présente ici six techniques différentes (bien que certaines soient des variations de la même idée).
Ces six techniques peuvent être retrouvées au fil des nombreux articles sur le sujet.
Il est conseillé de parcourir ces six essais du plus simple au plus échevelé :
- singleton-stdlib.rb
- singleton-metaclass.rb
- singleton-attr.rb
- singleton-module.rb
- singleton-object.rb
- singleton-last.rb
Objectif : Définir la classe memo-class
des classes qui mémorisent leurs instances. Pour simplifier, on pourra lui associer la classe memo-object
des objets qui sont mémorisés par leur classe.
Les mémo-classes s'approchent du design pattern Multiton. En Ruby, on peut les implémenter de différentes manières plus ou moins propres.
Objectif : définir une fonction inspect-object qui permette d'afficher le type d'un objet et les noms / valeurs de ses attributs.
En Ruby, toutes les fonctions de base d'un inspecteur d'objet sont définies dès l'origine dans le langage. Il est donc très facile de les réutiliser pour construire en quelques lignes des inspecteurs d'objet puissants.
Ici, on définit un module Inspector
qui affiche toutes sortes d'informations sur un objet donné et qui permet d'en effectuer une copie profonde et de le sérialiser de différentes manières.
OpenJava (mainteant OJ) est un langage basé sur Java, qui lui ajoute un MOP. OpenJava est relativement ancien (2002), mais son apport au Java vanilla reste toujours intéressant.
Projets liés : OpenC++ et Javassist
En OpenJava, les mémo-classes sont simulées en créant une méta-classe qui ajoutera à ses instances un attribut instances
et une méthode getInstances
.
MOP très puissant sur lequel se sont basés Ruby et Objective-C.