Surcharge d’opérateurs en Java Philippe Baqué Charles-Félix Chabert Yannis Juan Christian Delbé Encadré par Thomas Graf 8 avril 2002 Résumé Lancé en 1995 par Sun Microsystems, le langage Java ne cesse de s’imposer au grand public, et séduit de plus en plus d’utilisateurs. Ce succés est sans précédent, puisque jamais dans l’histoire de l’informatique, un langage de programmation n’a été aussi rapidement diffusé et adopté. Cette popularité ascendante est motivée par les nombreuses spécificités intéressantes du langage (qui a aussi bénéficié d’une trés large médiatisation). Parmis ces caractéristiques, les plus pertinentes nous semblent-être d’une part la capacité de Java à être facilement diffusé sur le réseau grâce à un interfacage trés simple avec celui-ci, d’autre part une interface de programmation très compléte et efficace, et enfin la fameuse portabilité de Java : “Write once, run everywhere”. Malgré cela, Java posséde quelques défauts inhérents à sa conception, à savoir une certaine lourdeur dans son interprétation. De même, il n’existe pas d’intermédiaire entre les types primitifs et les objets référencés, ce qui le condamne à être peu efficace en ce qui concerne le calcul numérique. Permettre aux utilisateurs de surcharger les opérateurs numériques nous semble être une bonne approche pour définir les bases de ce qui pourraitêtre une implémentation plus efficace et plus commode pour le calcul numérique en Java. Nous allons à travers ce rapport introductif voir les différentes approches que nous privilégions, celles que nous écartons, et enfin nos motivations. 1 1 Présentation du sujet Le sujet de ce projet est de proposer la spécification d’un modèle de surcharge d’opérateurs en Java, ainsi qu’une implémentation de référence. Cette implémentation sera intégrée au compilateur KJC [1]. Ce sujet, et la façon dont il a été posé, nous laissent à priori une grande liberté dans les choix de conception (approche par compilateur ou par préprocesseur, ...) et dans l’impact sur le langage Java (avec ou sans nouveau mot clé, modification de la grammaire, ...). Cette fonctionnalité est très peu présente sur le “marché” des compilateurs (ou préprocesseurs) Java étendu, peut-être parce que c’est un sujet très controversé : l’utilité et les effets néfastes de la surcharge d’opérateurs (on traitera de cette controverse dans la section 2). De plus, les quelques implémentations existantes sont assez pauvrement documentées, et pas toujours clairement spécifiées. Ce désert a rendu la tâche ardue mais attractive et concrète. On va montrer dans la suite de ce document comment nous allons essayer de construire un point d’eau dans ce désert. 2 Motivations 2.1 Les détracteurs De nombreux langages objets, comme C++, Eiffel ou C#, proposent la surcharge d’opérateurs. Mais Java n’implémente pas cette fonctionnalité. Nous nous sommes demandé pourquoi, et la raison qui semble ressortir est le souci de simplicité [2] : Java omits many rarely used, poorly understood, confusing features of C++ that in our experience bring more grief than benefit. These omitted features primarily consist of operator overloading (also the Java language does have method overloading), multiple inheritance, and extensive automatic coercions. Mais à quel prix ? C’est un vieux débat de l’informatique : simplicité contre expressivité. Guy Steele [3] apporte sa solution : l’évolutivité d’un langage, et qualifie Java de langage évolutif. 2 Alors, si cette évolutivité permettrait d’apporter à Java la surcharge d’opérateurs, pourquoi ne pas le faire? Certains répondrait : – “Si Sun ne l’a pas fait, c’est que ce n’est pas une bonne chose ou que c’est impossible !”. Nos lectures pendant la préparation de ce rapport nous ont appris à démythifier Sun, et à voir les programmeurs humains derrière ce sigle (on fera référence surtout à [4], qui présente les principaux défauts, selon l’auteur, de Java). De plus, si Java se dit évolutif, c’est bien pour le faire évoluer ! – “La surcharge d’opérateurs est un luxe dont on peut se passer.”. Alors, à l’extrême, pourquoi ne pas programmer en assembleur? – En ce qui concerne l’éternelle “illisibilité” que provoquerait la surcharge d’opérateurs, nous pensons que cet argument n’est pas recevable : même James Gosling [5], pourtant au début opposé à la surcharge d’opérateurs, avoue : And if you look at people who are doing complex arthmetic in Java right now, it’s really ugly. I mean, it’s unbelievably ugly. – “Un programmeur peut définir des opérateurs avec une sémantique bizarre, non-intuitive.”. Nous nous joignons à Conrad Weisert [6] pour répondre : Any programmer so undisciplined as to define + to do substraction is likely also to define add in the same misleading way or to commit all sorts of other programming sins. Competent professionals are unlikely to do either. 2.2 L’intérêt Le succés de Java est certes important, mais il y a une catégorie de programmeurs qu’il n’a pas conquis : les mathématiciens et tous ceux qui utilisent l’informatique à des fins majoritairement numérique. Ces personnes reprochent à Java sa lourdeur d’écriture et son manque d’efficacité à ce niveau. Un exemple souvent cité est l’utilisation des nombres complexes : on aimerait, pour ce type d’objet numérique, jouir des mêmes avantages que les types primitifs, c’est à dire efficacité, utilisation des opérateurs arithmétiques et sémantique par copie. Ainsi, nos recherches nous ont pratiquement toujours ramené à ce même thème : l’extension de Java au niveau numérique. On citera par exemple le projet JavaGrande [7], qui propose une approche intéressante que nous développerons dans la section 4. Le numérique semble donc être le talon d’achille de Java, et la surcharge des opérateurs serait un pas en avant vers un Java plus universel. 3 3 Les implémentations existantes Durant nos travaux de recherche préliminaires, nous avons rencontré deux approches bien distinctes. Tout d’abord, celle qui consiste à faire passer un pré-processeur sur le code et faire un remplacement textuel des opérateurs par les méthodes correspondantes, et une autre, plus subtile, qui consiste à introduire un nouveau type, pseudo-primitif, qui conserverait une sémantique de passage par valeur et certaines propriétés des “classes lourdes”. 3.1 Les préprocesseurs C’est le moyen le plus simple et le plus intuitif d’implémenter la surcharge d’opérateurs. Dans les définitions des classes, on implémente les méthodes surchargeant les opérateurs en leur donnant une syntaxe bien particulière. Ensuite, une fois notre programme principal écrit, on fait passer un préprocesseur qui se charge de remplacer textuellement les opérateurs par les appels de méthodes correspondants. C’est une approche purement syntaxique pour laquelle nous n’avons trouvé que peu d’exemples. L’un d’entre eux, JFRONT ): définit ses classes comme ceci (par exemple la classe ! "#$%#$'&#(%#)* ! +%,-). / 3 4 021 0 &5,-). 6 3 4 021780 95,-). 3 9%,:<;=&>3 4 Aprés passage du préprocesseur, on obtient le source suivant : "#$%#$'&#(%#)* ! +%,-). / 3 4 021 0 &5,-). 6 3 4 021780 95,-). 3 9%,+? 8@?&443 Nous avons également pu observer une autre approche, qui consiste à introduire un nouveau type, pseudo-primitif, pour lequel on pourrait surcharger les opérateurs. 3.2 Un type pseudo-primitif Il existe deux sortes de types en Java, les classes qui ont une sémantique par référence, et les types primitifs qui ont une sémantique par copie. L’idée est d’offrir à l’utilisateur un nouveau type, orienté numérique qui conserverait un peu des propriétés des deux précédents. Cette idée est lancée par Bill Joy ) , qui est une extension du langage Java écrite par [8]et est reprise par A Joseph D. Darcy [9]. Si l’utilisateur définit un nouveau type arithmétique, alors il est désirable que celui-ci ait une sémantique par copie, c’est à dire analogue à celle des types primitifs. De même, on aimerait conserver la notation infixe propre aux mathématiciens, d’ou la necessité de pouvoir surcharger les opérateurs. & Borneo introduit un nouveau mot-clé, qui agit comme un modifica& teur de classe. Seules les classes déclarées comme peuvent définir des méthodes permettant la surcharge d’opérateurs. De plus, pour pouvoir per) inimettre à ces classes de se comporter comme des types primitifs, A tialise par défaut tous les champs de ces classes. 4 Notre approche L’approche que nous allons présenter ici est celle qui nous semble aujourd’hui la plus intéressante : c’est celle qui consiste à introduire un type pseudo-primitif. Elle est axée principalement sur l’extention de Java au niveau numérique, et s’appuie sur les propositions de JavaGrande [7], ainsi que sur l’implémentation existante Borneo [9]. 5 4.1 Critique des approches précédentes La section précédente nous a présenté les différentes approches que nous avons pu rencontrer. L’idée la plus intuitive est celle d’un préprocesseur, mais un tel système de réecriture ne tiendrait compte d’aucune sémantique, et laisserait trop de responsabilités au programmeur quant à la définition de cette sémantique. De plus, ce système n’apporterait rien d’autre qu’une facilité d’écriture et n’enrichirait pas la sémantique de Java. On aurait alors un outil pour Java et non plus une extension de Java. Une seconde approche, que nous n’avons pas rencontrée mais qui est celle de C++, serait d’intégrer la surcharge d’opérateurs dans le compilateur de façon “totale”, c’est à dire permettre de définir et de surcharger tous les opérateurs pour toutes les classes. Bien que sémantiquement plus riche et plus robuste que l’approche par préprocesseur, cette méthode ne nous a pas séduite parceque la sémantique par référence de Java n’est pas adaptée à la surcharge d’opérateurs et au calcul numérique en général. 4.2 Notre choix L’approche que nous avons choisie s’inscrit dans un projet de plus large envergure, l’extension de Java au niveau numérique. Ce projet est principalement représenté par JavaGrande [7], qui comporte d’autres points que la surcharge d’opérateurs que nous n’aborderons pas ici. En effet, de part son état de langage objet qui rend toutes données, ainsi que leur manipulation, “lourdes”, et de part sa sémantique par référence (hors types primitifs), Java n’est pas un langage numérique. Les opérateurs arithmétiques ne sont utilisables qu’avec les types primitifs, #)C . Ils utilisent un passage de paramètres par exception faite de la classe B valeur. La surcharge d’opérateurs demande donc de garder cette sémantique pour le calcul numérique si l’on veut lever le maximum d’ambiguités dans son utilisation, or les objets en Java ne sont manipulés que par référence. C’est pour cette raison que la surcharge d’opérateurs nécessite un type particulier permettant de simuler un passage par valeur. Il est important de noter que ce type simule le passage par valeur, et sera finalement considéré par le compilateur comme une référence, puisque la JVM n’accepte d’empiler que des références, ormis les types primitifs. C’est donc pour ce nouveau type, et seulement pour lui, que l’on permettra la surcharge des opérateurs arithmétiques, incluant l’affectation et la comparaison. 6 4.3 Nos objectifs Le planning de réalisation ne peut être établi de manière précise pour l’instant mais sera bientôt disponible sur le site du projet. Il nécessiterait une formalisation plus précise de l’approche et de ses implications, qui n’a pas pu être faı̂te pendant nos premières recherches, principalement bibliographiques. Nous définirons ensuite une spécification qui devra : – minimiser l’impact sur Java, c’est à dire être une extension, pas une modification de la JLS, – ne nécessiter aucune modification de la JVM, – définir un type hybride, avec une sémantique par copie, – permettre la surcharge d’opérateurs pour ce type. Une implémentation de référence sera réalisée et intégrée au compilateur Java KJC [1]. 7 5 Bibliographie 1. 2. 3. 4. 5. 6. 7. 8. 9. @EFF ... ( FG#F($FG# D KJC Reference Manual. D . Sun Microsystems. The Java Language : An Overview. G. L. Steele Jr. Growing a Language. 1998. C. Weisert. The Dark Side of Java. 1997. D. Boloker, C. Rumble, D. Tidwell. James Gosling : An interview with the Java Guru. 1999. C. Weisert. Conventions for Arithmetic Operations in Java. 1997. Java Grande Forum Report. 1998. B. Joy Proposal for Enhanced Numeric Programming facilities in Java, Draft 3. 1997. J. D. Darcy. Borneo 1.0.2. 1998. Ces documents, ainsi qu’une mise à jour de l’état d’avancement du projet, EFF$# )#$ : F ("$F H sont disponibles sur D . 8