Présentation du langage NICE

Ricky81

Cet article vous présente un langage original, NICE, très proche du Java et introduisant des concepts nouveaux par rapport à Java, mais néanmoins anciens si on parcourt le spectre des langages de programmation existants.
Le but de cet article est de vous présenter le langage NICE, les aspects communs avec Java et d'autres langages de programmation, le tout à travers de nombreux exemples. ♪

- Titre : Présentation du langage NICE

- Auteur : Ricky81

- Parution : 25 mai 2004

- Licence : Copyright ® 2004 Ricky81. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- Avant-propos

- Remerciements

- I. Rapports entre Nice et Java

- I-A. Le compilateur

- I-B. La philosophie et l'exécution d'un programme

- I-C. Confirmation d'un lien de parenté

- I-D. Interopérabilité avec Java

- II. Java 5 vu par Nice

- II-A. La généricité

- II-B. Les optimisations

- II-B-1. Boucle for

- II-B-2. L'autoboxing

- III. Et bien plus encore…

- III-A. La sécurité et l'efficacité avant tout

- III-A-1. Détecter davantage d'erreurs à la compilation

- III-A-2. Comment faire disparaître l'exception NullPointerException

- III-B. Un effort sur la modularité

- III-B-1. Les méthodes locales

- III-B-2. Des méthodes et variables de paquetage

- III-B-3. Des méthodes prenant en paramètre d'autres méthodes

- III-B-4. Les multimethods

- III-B-4-a. Le multiple-dispatch

- III-B-4-b. La flexibilité apportée dans la déclaration de méthodes (Value Dispatch)

- III-B-5. Les interfaces abstraites

- III-C. L'écriture de code intelligent (« Expressivity »)

- III-C-1. La notation

- III-C-1-a. Le passage de paramètres

- III-C-1-b. L'appel des méthodes

- III-C-2. Le Design By Contract

- III-C-3. Les valeurs par défaut dans les méthodes

- III-C-4. Les constructeurs en Nice

- III-C-5. Le renvoi de tuples

- IV. L'avenir de Nice

- IV-A. La release 1.0 approche

- IV-B. Un plugin pour Eclipse

- IV-C. Le projet NiceSwing

- IV-D. Quelques liens…

- IV-D-1. … concernant Nice

- IV-D-2. … pour approfondir certains concepts abordés

Avant-propos

Alors qu'on continue de parler des nouveautés de Java 5 et que certains attendent avec impatience la sortie officielle, les évolutions dans le monde de la programmation autour de Java ne s'arrêtent pas pour autant. Java va proposer des fonctionnalités de codage qui existent déjà pour d'autres langages, par exemple le C++ (avec les templates ou types génériques) ou le langage Ada qui permet de faire des choses depuis plus de 20 ans (et bientôt 10 pour la version orientée objet) que Java va en partie intégrer prochainement. Ces remarques sont bien entendu faciles puisqu'elles concernent les langages précédant Java.
Un sujet hautement intéressant est la comparaison avec un langage qui, basé sur le langage Java, propose des fonctionnalités de codage et de compilation plus avancées que Java et qui prend déjà en charge depuis un certain moment des fonctionnalités attendues avec Java 5.
Ce langage est le langage Nice (initialement appelé Bossa) dont le principal initiateur est Daniel Bonniot de l'INRIA.

Je vous propose donc de vous présenter Nice au cours de cet article, et je commencerai évidemment par vous présenter les potentielles raisons qui pourraient vous pousser à suivre l'évolution de ce langage et pourquoi pas l'utiliser pour vos développements futurs.

Remerciements

Un grand merci à la section Java ainsi qu'à Anomaly pour les corrections.

I - Rapports entre Nice et Java

Nice n'est pas vraiment un nouveau langage dans la mesure où il s'inspire totalement de Java. Vous allez pouvoir constater que Nice fait mieux que reprendre les concepts, principes et notations présents dans Java, et qu'un développement en Nice est loin d'être incompatible avec Java.

I-A - Le compilateur

Premier aspect qui a son importance, le compilateur Nice a été écrit en Java. Cela nécessite notamment de disposer d'une JVM 1.2 ou supérieure. Ce compilateur génère du bytecode Java, qui va pouvoir être exécuté comme n'importe quelle application développée en Java. La particularité de la compilation Nice réside dans le fait de compiler des paquetages et non des fichiers.
Nice a donc un lien de parenté évident avec Java, mais nous allons progressivement constater que ce lien de parenté ne saurait se limiter à une relation du type père-fils.

I-B - La philosophie et l'exécution d'un programme

Le langage reprend les grands principes de Java, à savoir la hiérarchisation en paquetages. Nice reprend également l'ensemble des mots-clés et de la syntaxe du langage Java.
Les grands principes et mots d'ordre de Nice sont la modularité, la sécurité, et la production de code intelligent (« Expressivity » en anglais). Nice peut ainsi permettre aux développeurs une meilleure réutilisation du code, de mettre en pratique des modèles moins conventionnels qui posaient problème avec Java. Le langage permet également la détection de certaines exceptions directement à la compilation, là où elles pourraient se déclencher aléatoirement en fonctionnement pour un programme similaire en Java. Enfin, Nice apporte un certain nombre d'éléments au niveau du codage de tâches classiques comme les boucles ou les traitements sur des ensembles. Ces apports permettent de réduire considérablement la taille et la complexité du code par rapport à ce qu'on peut avoir l'habitude de faire avec Java.

La commande pour lancer la compilation est similaire à celle utilisée pour Java : on appelle la commande nicec comme on appellerait la commande javac (aux options de commande près).
Pour lancer le programme, il n'y a pas de secret : un traditionnel java monfichier suffira puisque le compilateur Nice produit du bytecode Java.
Enfin, tout comme Java dispose de la Javadoc, Nice propose la Nicedoc.

I-C - Confirmation d'un lien de parenté

Nice permet donc, tout comme Java, la déclaration d'une classe ou d'une interface dans un fichier. Néanmoins, dans la philosophie de Nice, l'écriture de la méthode statique classique main n'est pas recommandée : cette méthode n'apportant rien de particulier à la classe, il est préférable de la déclarer à part dans un fichier. En effet, ce qui n'est pas possible en Java, l'est avec Nice : à savoir uniquement déclarer une méthode dans un fichier à l'intérieur d'un paquetage. Mais cela n'est pas uniquement un atout pour déclarer la méthode main à part, nous verrons par la suite que cet aspect offre de formidables opportunités en termes de modularité.

Un autre lien de parenté, que certains pourront voir dans le sens Nice -> Java, est la possibilité de bénéficier des fonctionnalités annoncées pour Java 5 avec Nice. En effet, Nice dispose déjà de la généricité (types paramétrés), de boucles for plus efficaces, ou encore l'autoboxing.

I-D - Interopérabilité avec Java

La relation Java-Nice ne se limite pas à une relation du type Père-Fils. En effet, comme on pouvait s'y attendre (similitudes flagrantes dans l'utilisation de paquetages et la déclaration de classes) Nice est hautement compatible avec tout code Java existant. Il est donc non seulement possible de faire appel aux bibliothèques de classes classiques comme java.util, mais bien évidemment également toute classe définie par vos soins. Il est notamment possible d'hériter d'une classe Java ou d'implémenter une classe Java.
Jusque là, le lecteur pourrait se dire que le langage Nice est certes séduisant et introduit des concepts intéressants, mais que de parier sur ce langage pouvait être risqué, car il n'offre pas la même pérennité que Java. Un aspect qui pourra alors en intéresser certains est le suivant : étant donné que Nice produit du bytecode Java, il est possible d'utiliser des classes Nice dans votre code Java. Il y a certes des limitations, notamment l'impossibilité de surcharger une méthode Nice en Java, mais il est tout à fait envisageable de faire une partie du développement d'une application Java en Nice.

II - Java 5 vu par Nice

Comme cela a été dit précédemment, Nice dispose déjà des principales fonctionnalités annoncées pour Java 5. Le but des paragraphes suivants est donc d'évoquer certaines de ces fonctionnalités et de les illustrer par du code Java 5 et Nice.

II-A - La généricité

L'utilisation de classes génériques (ou encore nommées classes paramétrables) s'avère extrêmement utile et appréciée sur de nombreux points. En effet, le code sera plus agréable à lire et à maintenir, les risques d'erreur ou d'oubli dans les conversions puisqu'elles sont faites automatiquement. Enfin, cela favorise la réutilisation de composants, et permettra au développeur de se concentrer sur les objets métier.

Voyons sur un petit exemple (Langage sera une classe métier par exemple) à quel point la généricité peut rendre service :

Avec Java 1.4

ArrayList al = new ArrayList();
al.add(new Langage("Java"));
al.add(new Langage("Nice")); // on pourrait très bien passer un autre type d'objet à la méthode add

for(Iterator i = al.iterator(); i.hasNext();) // clause for plutôt "lourde"
{
Langage l = (Langage) i.next();
/* on récupère l'objet avec transtypage obligatoire et risque d'erreur
si l'objet n'est pas du type Langage ... */
System.out.println(l); // affichage de la valeur de l.toString();
}

Avec Java 5

ArrayList<Langage> al = new ArrayList<Langage>();
al.add(new Langage("Java"));
al.add(new Langage("Nice")); // on ne peut plus passer d'objet d'un autre type

for(Iterator i = al.iterator(); i.hasNext();)
// nous verrons plus loin que d'autres nouveautés Java 5 vont améliorer le parcours
{
Langage l = (Langage) i.next();
System.out.println(l);
}

Ce dernier code fonctionne parfaitement avec Nice, mais je vous propose dès à présent un avant-goût des possibilités de Nice sur cet exemple (l'ensemble des autres particularités sera vu au chapitre suivant).

II-B - Les optimisations

II-B-1 - Boucle for

Les nouveautés de Java 5 concernent également les boucles de parcours d'ensembles (collections et tableaux). Reprenons l'exemple précédent pour illustrer cette fonctionnalité.

Avec Java 5

ArrayList<Langage> al = new ArrayList<Langage>();
al.add(new Langage("Java"));
al.add(new Langage("Nice"));

for(Langage l : al)
{
System.out.println(l);
}

Les possibilités d'utilisation ne sont bien entendu pas limitées qu'aux collections et tableaux. Cela est possible pour toute classe implémentant l'interface Iterable.

for(Type Identifier : Expression) // Expression doit implémenter Iterable

Nice n'est pas en reste. Il fait même mieux qu'un opérateur for évolué, il propose la méthode foreach.

Avec Nice

ArrayList<Langage> al = new ArrayList();
al.add(new Langage("Java"));
al.add(new Langage("Nice"));
al.foreach(Langage l => System.out.println(l));

Nous reviendrons plus loin sur la méthode foreach et sur les fonctionnalités qui se cachent derrière. Je vous laisse constater la puissance et la simplicité de cette méthode qui permet à la fois de parcourir un ensemble et de faire un traitement sur chaque élément.

II-B-2 - L'autoboxing

Qui n'a jamais trouvé la conversion d'un objet d'un type enveloppe vers le type non objet correspondant (et vice versa) particulièrement lourde ? Bien heureusement, Java 5 permet de se passer de telles conversions.
Voici un petit exemple pour illustrer ceci :

Avec Java 1.4

ArrayList al = new ArrayList();
al.add(0, new Double(1.4));
double version = ((Double) al.get(0)).doubleValue();

Avec Java 5

ArrayList<Double> al = new ArrayList<Double>();
al.add(0,1.5);
double version = al.get(0);

III - Et bien plus encore…

Après avoir montré que Nice était relativement proche de Java et que des classes Nice pouvaient interagir avec des classes Java, je vous propose maintenant une partie sur des aspects propres au langage Nice.

III-A - La sécurité et l'efficacité avant tout

Comme cela a déjà été évoqué, Nice fait un effort important sur les aspects sécurité et efficacité de programmation. En détectant des erreurs, qui apparaissent en général lors de l'exécution, à la compilation, Nice propose à la fois d'accompagner le développeur dans la détection de certaines erreurs, mais permet également d'assurer le non-déclenchement de ces mêmes erreurs à l'exécution.

III-A-1 - Détecter davantage d'erreurs à la compilation

Le développeur n'est pas parfait, un moment d'égarement peut être à l'origine d'erreurs minimes à conséquences importantes (non initialisation de variables… ). Les capacités du développeur résident dans sa faculté à réfléchir sur des aspects complexes pour arriver à modéliser son application. Tout le contraire du développeur, le compilateur ne sait pas réfléchir (ce n'est pas lui qui va corriger des erreurs de modélisation), mais il est capable de prendre en charge des tâches plus proches du code et notamment sur la vérification de certains aspects comme l'initialisation de variables ou les erreurs prévisibles à l'exécution. Le travail du compilateur est de valider le code écrit, et on pourrait presque aller jusqu'à dire qu'idéalement un code qui compile ne doit pas générer d'erreurs lors de l'exécution (c'est évidemment utopique puisque certains aspects dynamiques comme la connexion à une base de données ou l'ouverture d'un fichier ne peuvent pas vraiment être pris en compte à la compilation). En anglais, cela s'appelle « static safety ».

III-A-2 - Comment faire disparaître l'exception NullPointerException

La détection des appels sur les objets susceptibles d'être à null fait notamment partie des vérifications faites par le compilateur Nice.

Le compilateur Nice contrôle donc si les références (sous-entendu pointeurs) sont à null ou pas. Étant donné qu'il faudrait enrichir le code de tests pour chaque variable susceptible de prendre la valeur null, cette initiative permet donc au développeur de se concentrer sur la partie utile du code en laissant au compilateur de faire les vérifications pour lui.
Néanmoins, il y a toujours ces variables qui restent susceptibles d'être à null, et qu'il faut donc distinguer des autres sous peine de voir le compilateur renvoyer une erreur. Une notation spécifique a donc été introduite en préfixant le type dans la déclaration par un point d'interrogation :

?String nom;

Cette déclaration permet au compilateur de savoir que la variable est susceptible d'être à null et que le développeur devra prendre en charge le test de la variable :

int longueur = (nom == null) ? 0 : nom.length();

III-B - Un effort sur la modularité

Un second point fort de Nice est la modularité. Tout en imposant au développeur une certaine rigueur dans le code (notamment à travers la détection d'erreurs à la compilation), Nice propose un certain nombre de nouveautés par rapport à Java, lesquelles permettent d'avoir un code plus clair, de supprimer la redondance, et d'offrir aux développeurs des moyens efficaces d'implémenter des modèles pour lesquels ils pourraient être amenés à « bricoler » avec d'autres langages.

III-B-1 - Les méthodes locales

Il est possible avec Nice de définir des méthodes locales à l'intérieur d'une autre méthode. Cela peut par exemple permettre d'alléger un traitement répétitif pour lequel la méthode qu'il faut définir n'a de sens et ne va qu'être appelée dans une autre méthode. Il peut donc être intéressant de ne pas laisser cette méthode visible par les autres méthodes, et par conséquent de limiter les possibilités de l'appeler à une méthode particulière.
Voici un exemple illustrant ce propos :

class Local
{
void afficher()
{
void afficherLocal(String s)
{
System.out.print("Texte à afficher : ");
System.out.println(s);
}

afficherLocal("a");
afficherLocal("b");
}
}

Le résultat est le suivant :
Texte à afficher : a
Texte à afficher : b

III-B-2 - Des méthodes et variables de paquetage

Dans un paquetage Nice, il est possible de définir des méthodes et des variables/constantes indépendantes de toute classe. L'apparition de méthodes et variables de paquetage dans le langage Nice entraîne la disparition des méthodes et variables de classes (c'est-à-dire static). L'approche de Nice ressemble donc (à la structure de fichiers près) à ce qui est employé par le langage Ada (plus précisément Ada95) et met donc l'accent sur la notion de paquetage.

Conséquence immédiate de ce choix : étant donné que les méthodes statiques n'existent pas avec Nice, il n'y a donc pas la fameuse méthode public static void main(String[] args). On retrouve cette méthode comme méthode de paquetage : void main(String[] args).

Étant donné que l'approche de Nice attribue une place importante à la notion de paquetage, quoi de plus logique pour le compilateur de compiler ces derniers. En effet, contrairement à Java, l'unité de compilation de Nice est le paquetage et cela est parfaitement cohérent avec ce qui a été dit précédemment. Il est donc naturel de n'avoir qu'une méthode main dans un paquetage MonPaquetage qui sera appelée par java -jar MonPaquetage.jar.

III-B-3 - Des méthodes prenant en paramètre d'autres méthodes

Le lecteur attentif aura décelé un aspect important de Nice dans la syntaxe de la méthode foreach, à savoir la possibilité pour une méthode de prendre en paramètre une autre méthode.

Ainsi, la méthode foreach prend en paramètre la méthode qui va être appelée sur les différents éléments de la collection en question.

III-B-4 - Les multimethods

Ceux qui connaissent le langage Ada sont familiers des notations du type f(x,y,z) pour l'appel de méthode. Cette notation a pour effet d'harmoniser l'appel des méthodes qu'elles soient définies au niveau d'un paquetage ou au niveau d'une classe. En effet, pour une méthode de classe, le premier paramètre correspond dans cette notation à l'objet qui, dans des langages comme Java, appelle la méthode. Ainsi, la notation f(x,y,z) en Ada correspond à la notation x.f(y,z) en Java.

Étant donné que Nice met l'accent sur la notion de paquetage, et toujours dans un souci de forte modularité, il prend en charge les deux notations. Nous allons voir à travers les exemples suivants ce que la prise en compte de la notation f(x,y,z) peut permettre.

Voici un exemple simple :

class MoyenDeTransport
{
String marque;
int masse;

String affiche();
}

class Camion extends MoyenDeTransport
{
int nbRoues;
}

affiche(MoyenDeTransport m)
{
return m.marque + " (" + m.masse + " kg)";
}

affiche(Camion c)
{
return c.marque + " (" + c.masse + " kg, " + c.nbRoues + " roues)";
}

Bien que la déclaration se fasse en dehors de la classe, il est possible d'appeler la méthode affiche() à partir d'un objet comme on le fait habituellement en Java par o.affiche();

III-B-4-a - Le multiple-dispatch

class Aliment
{
String nom;
float prix;
}

equals(Aliment a1, Aliment a2) = a1.nom.equals(a2.nom) && a1.prix==a2.prix;

Avec ce petit morceau de code, vous pourrez très bien exécuter le code suivant :

void main(String[] args)
{
Aliment ppois = new Aliment(nom: "petits pois", prix: 0.8);
Aliment carottes = new Aliment(nom: "carottes", prix: 0.75);

if(carottes.equals(ppois))
{
println("Ce ne sont pas les mêmes aliments.");
}
}

Qu'apporte ce code par rapport à la version équivalente en Java (qui consiste à déclarer la méthode equals au sein de la classe Aliment directement) me direz-vous. Premièrement, cela vous évite d'avoir à tester si l'objet passé en paramètre à equals est bien du type Aliment ainsi que de procéder à un cast.
De plus, cela permet tout simplement d'adapter et de faire évoluer le contenu d'un paquetage sans avoir à modifier les classes. En effet, imaginez qu'une classe AlimentEnVrac héritant de la classe Aliment soit créée et qu'il faille définir l'opérateur equals pour tester l'équivalence entre un objet Aliment et un autre AlimentEnVrac. En Java, il vous faudrait éditer la classe Aliment et y rajouter cette méthode, alors qu'avec Nice vous n'avez qu'à rajouter la méthode au niveau du paquetage.

class AlimentEnVrac extends Aliment
{
// prix correspondra ici au prix au kg
float quantite; // en kg
}

equals(Aliment a1, AlimentEnVrac a2) = a1.nom.equals(a2.nom) && (a1.prix==(a2.prix*a2.quantite));

III-B-4-b - La flexibilité apportée dans la déclaration de méthodes (Value Dispatch)

L'utilisation des i« multimethods » constitue un apport considérable pour la lisibilité et la maintenance de certains types de méthodes.
Imaginez le problème suivant : vous devez écrire une méthode qui renvoie un booléen en fonction de la valeur qui lui est fournie. L'écriture de la méthode va donc correspondre à un amoncellement de if et de return.

L'utilisation du Value Dispatch en Nice permet actuellement de traiter les méthodes prenant en paramètre le type integer, boolean, string, character, une énumération (enum) ou encore une instance constante de classe.
Voici comment cela se matérialise pour notre exemple :

class Personne
{
String nom;

boolean faitPartieEquipe()
{
return membreEquipe(nom);
}
}

boolean membreEquipe(String s);

membreEquipe("Arnaud") = true;
membreEquipe("Eric") = true;
membreEquipe(s) = false;

Même si l'exemple n'est pas forcément très pertinent, on constatera qu'il est très facile de mettre à jour le code en rajoutant un nouveau cas pour membreEquipe dans le cas où l'équipe est modifiée.

III-B-5 - Les interfaces abstraites

Dernier point concernant la modularité, la possibilité de créer et d'implémenter des interfaces dites abstraites. Ce sont des interfaces qui ressemblent en tout point à celles qu'on a l'habitude de manipuler en Java, à un détail près. En effet, une interface abstraite Nice est beaucoup moins riche qu'une interface Java, car elle n'a pour unique but que de lister les méthodes que les classes l'implémentant devront définir. De plus, avec les différents concepts que nous avons pu voir, il est possible de préciser qu'une classe déjà créée implémente une interface abstraite (attention ce n'est pas vrai pour une interface classique) sans avoir à la modifier directement.

Pour ceux qui ne verraient pas en quoi cela se distingue de ce qui est fait en Java, il faut savoir qu'il est possible d'utiliser le nom d'une interface dans la déclaration d'une variable comme dans ce qui suit :

interface MonInterface
{
...
}

class MaClasse implements MonInterface
{
...
}

class MonMain
{

MonMain()
{
};

public static void main(String[] args)
{
MonInterface test = new MaClasse();
}

}

Les interfaces abstraites Nice ne peuvent donc en quelque sorte pas être considérées comme une classe et leur rôle se limite à de la spécification.

L'exemple précédent, repris avec une interface abstraite en Nice pourrait prendre la forme suivante :

abstract interface MonInterface
{
void maMethode();
}

class MaClasse
{
... // déclarations propres à la classe (ne tient pas compte de l'interface)
}

class MaClasse implements MonInterface; // peut très bien apparaître dans un autre fichier

maMethode(MaClasse c)
{
...
}

III-C - L'écriture de code intelligent (« Expressivity »)

Le dernier aspect mis en avant par Nice est l'écriture de code intelligent, c'est-à-dire l'introduction de certains concepts permettant à la fois de rendre un code plus expressif et donc maintenable, mais également la mise en place de moyens évitant au développeur un code répétitif et difficile à comprendre.

III-C-1 - La notation

On vous a probablement plusieurs fois dit que la première étape dans l'écriture d'une méthode était de lui trouver un nom assez explicite pour qu'un autre développeur puisse facilement comprendre à quoi elle sert. Lorsque le nombre d'arguments de la méthode est supérieur à un, les mêmes exigences se répercutent sur la liste des arguments, arguments que l'on peut en règle générale également nommer de façon intelligente pour faciliter la compréhension.

Néanmoins, ces efforts ne seront bénéfiques qu'au développeur qui va appeler ces méthodes et leur passer des paramètres, car l'environnement de développement qu'il utilise pourra le guider et lui indiquer les noms et types des paramètres par exemple. Au contraire, toute personne qui va relire le code va être obligée de consulter constamment la documentation pour mieux comprendre l'appel d'une méthode.

C'est pour améliorer et faciliter la compréhension et le développement que Nice propose certaines fonctionnalités originales, dont certaines bien connues par les utilisateurs du langage Ada.

III-C-1-a - Le passage de paramètres

Premier aspect important : l'appel d'une méthode en Nice peut se faire de manière classique en passant dans l'ordre de déclaration les paramètres, mais également le passage de paramètres par nom.
Le passage de paramètres par nom permet de s'affranchir de l'ordre d'appel des paramètres et rend le code davantage compréhensible et maintenable. Voici quelques exemples d'utilisation :

Deux déclarations identiques

Voiture v1 = new Voiture(Carburant: "Essence", Couleur: "Gris", Constructeur: "Peugeot", Modele: "206");

Voiture v1 = new Voiture(Constructeur: "Peugeot", Modele: "206", Carburant: "Essence", Couleur: "Gris");

Comment rendre un code extrêmement lisible

void vendre(Voiture voiture, Personne acheteur, float prix) { ... }

vendre(prix: 12000, voiture: v1, acheteur: dupont);

Cette possibilité, conjuguée à la notion de multiméthodes vue précédemment, rend le code beaucoup plus explicite et permet d'éviter les erreurs classiques qu'on peut rencontrer lorsqu'une méthode dispose d'un grand nombre de paramètres.

III-C-1-b - L'appel des méthodes

Comme cela a déjà été dit, l'appel d'une méthode avec Nice peut se faire d'un point de vue objet par x.f(y,z) ou d'un point de vue paquetage par f(x,y,z). L'introduction du passage des paramètres par nom permet même d'appeler cette même méthode par f(param3: z, param2: y, param1: x);

III-C-2 - Le Design By Contract

Nice supporte des préconditions et des postconditions telles qu'on peut en définir lorsqu'on précise certains aspects de notre modèle avec O.C.L. (Object Constraint Language).
Les mots-clés introduits par Nice sont requires et ensures.

Voici l'exemple donné par les auteurs de Nice :

interface Buffer<Elem>
{
int size();

boolean isFull();
boolean isEmpty() ensures result == (size() == 0);

void add(Elem element)
requires
!isfull() : "buffer must not be not full"  // A comma here is optional
ensures
!isEmpty() : "buffer must not be empty",   // Note the comma
size() == old(size()) + 1 : "count inc";
}

III-C-3 - Les valeurs par défaut dans les méthodes

Qui n'a pas déjà été confronté en Java à l'écriture d'une méthode pouvant avoir des paramètres optionnels qui deviennent, lorsqu'ils ne sont pas fournis, des valeurs par défaut ? Dans ce type de situation, le développeur est contraint de déclarer autant de variantes de la méthode qu'il y a de variantes occasionnées par les paramètres optionnels (pour 2 paramètres optionnels, on pourrait être amené à définir 4 variantes de la méthode, pour 3 on arrive déjà à 8).
Nice propose une gestion très efficace des valeurs par défaut, laquelle permet de ne définir qu'une seule méthode, quel que soit le nombre de paramètres optionnels (il est bien entendu que certains concepts vus précédemment facilitent l'introduction des paramètres optionnels et valeurs par défaut).

Voici quelques exemples :

void copier(File de, File vers, int tailleBuffer = 1024) { ... }
copier(tailleBuffer: 256, vers: f2, de: f1);

void extraire(File de, File vers, long taille = de.length()) { ... }
extraire(de: f1, vers: f2);

Vous voyez donc qu'il est possible de spécifier une valeur par défaut dépendant d'un autre paramètre de la méthode. L'utilisation de valeurs par défaut facilite considérablement la compréhension et le codage d'une méthode.

III-C-4 - Les constructeurs en Nice

Dans le même esprit de simplification, Nice permet de s'affranchir de l'écriture des constructeurs. Il ne vous est pas possible de définir votre propre constructeur, et Nice crée par lui-même un constructeur à partir des attributs que vous avez définis dans votre classe.
Il y a bien évidemment une raison à ce choix, et c'est là que rejaillit l'aspect sécurité mis en avant par Nice. En effet, en laissant l'utilisateur définir un constructeur comme il peut actuellement le faire en Java, il lui est possible d'aboutir à des erreurs de fonctionnement inattendu :

class Parent
{
Parent()
{
Logger.log("Crée: " + this);
}

public String toString()
{
return "Parent";
}
}

class Enfant extends Parent
{

Enfant(File fichier)
{
this.fichier = fichier;
}

private File fichier;

public String toString()
{
return "Enfant, taille du fichier: " + fichier.length());
}
}

À première vue, on ne voit pas pourquoi ce code serait susceptible d'entraîner un comportement inattendu. Pourtant, si nous créons un nouvel objet de type Enfant, l'appel sera d'abord fait au constructeur Parent et celui-ci va appeler la méthode toString() par l'intermédiaire de la ligne Logger.log(« Crée: » + this);. Or, à cet instant précis, l'objet n'a pas encore son champ fichier et il va donc y avoir déclenchement d'une exception NullPointerException.

C'est donc un argument suffisant pour justifier une réflexion sur la façon de définir un constructeur, et c'est ce que Nice se propose de faire. En effet, il est actuellement possible d'appeler un constructeur avec pour paramètres l'ensemble des champs définis dans la classe. Pour être complet, il faut également préciser qu'il n'y a pas pour autant de contrainte au niveau du constructeur : au contraire, il est obligatoire de spécifier dans le constructeur au moins les champs pour lesquels aucune valeur n'est définie dans leur déclaration. Enfin, il faut savoir que l'appel du constructeur se fait exclusivement en passant les paramètres par nom.

L'état actuel du constructeur d'une classe Nice n'est bien évidemment pas satisfaisant, car l'auteur de la classe peut souhaiter définir les champs pouvant être initialisés ou non dans le constructeur. Les auteurs de Nice semblent se diriger vers un marquage spécial de ces champs, lequel permettrait de faire cette fameuse distinction.

Aux dernières nouvelles, il est désormais possible de définir ses propres constructeurs et de définir une section particulière qui sera exécutée à chaque création d'un objet de la classe, avec bien entendu des contraintes (exemple repris) :

class Point
{
double x;
double y;

{
System.out.println("Création d'un point.");
}
}

new Point(double angle, double distance)
{
this(x: distance * cos(angle), y: distance * sin(angle));
}

Quelques commentaires sur ce code. Il est donc possible de définir un constructeur (à l'extérieur de la classe) qui va lui même appeler le constructeur défini par Nice à partir des champs de la classe. Dans ce constructeur, il n'est pas possible d'accéder à l'objet this et il n'y a donc aucun risque d'appeler une méthode susceptible d'être redéfinie dans une classe enfant.
De plus, la section délimitée par des accolades à l'intérieur même de la classe, permet de préciser un code à exécuter après la création d'un objet. Cela répond au problème précédemment identifié, car ce code sera exécuté après l'initialisation complète des champs.

III-C-5 - Le renvoi de tuples

Dernier aspect intéressant du langage Nice que je voulais citer, la possibilité de définir une méthode renvoyant un tuple permet au développeur une plus grande flexibilité et peut lui éviter par exemple d'avoir à créer une classe intermédiaire.

Voici un exemple d'utilisation :

(int, int) minMax(int x, int y) = x < y ? (x, y) : (y, x);

void afficherCouple((int, int) couple)
{
(int x, int y) = couple;
System.out.println("(" + x + ", " + y + ")");
}

void main(String[] args)
{
printTuple(minMax(14, 17));
printTuple(minMax(42, 41));
}

IV - L'avenir de Nice

IV-A - La release 1.0 approche

La version 1.0 de Nice n'est pas loin (au moment de la rédaction de cet article, la dernière version était la 0.97), et il est probable que cette première version majeure intéresse un public plus large que le public actuel.
Les versions antérieures à la 0.97 (celle-ci comprise) ne prennent pour l'instant pas en charge les mots clés private et public, et la version 1.0 devrait combler ce manque et donner aux utilisateurs de Nice les moyens de développer des applications d'un niveau approchant le niveau de celles développées en Java.

IV-B - Un plugin pour Eclipse

Preuve que Nice est un langage reconnu, il existe un plugin Nice pour Eclipse entièrement écrit en Nice et qui permet de manipuler des fichiers et paquetages Nice dans un environnement de développement (coloration syntaxique, gestion de propriétés) avec l'intégration du compilateur (compilation, progression, affichage des erreurs et avertissements).
L'enrichissement de ce plugin par d'autres fonctionnalités (gestion de préférences, débogage… ) ne devrait pas tarder.

IV-C - Le projet NiceSwing

Comme cela a été dit plus haut, il est tout à fait possible d'utiliser des bibliothèques Java dans du code Nice pour construire vos applications. Dans ce cadre, un projet appelé NiceSwing a été initié dans le but de fournir une bibliothèque Nice facilitant la création d'applications Swing en utilisant toute la puissance de Nice.
Ce projet n'est en aucun cas la programmation d'une bibliothèque graphique propre à Nice. Il s'agit tout simplement de proposer une surcouche des composants utilisés actuellement en Java en exploitant les possibilités de Nice pour permettre au développeur de créer et gérer ces applications de manière plus efficace.

IV-D - Quelques liens…

IV-D-1 - … concernant Nice

- Nice sur Sourceforge

- Un comparatif entre Jython et Nice

- Description de NiceSwing

- Description du plugin pour Eclipse

IV-D-2 - … pour approfondir certains concepts abordés

- Présentation de Java 5 Tiger par Lionel Roux

- Discussion sur Java 5

- Le langage Ada