décembre
2009
C’est fait, le projet Lambda est né de la volonté d’intégrer un système de closures, que l’on nommera désormais « expressions lambda ».
On y retrouve donc une liste de diffusion mais surtout un prémice de la proposition : Project Lambda : Straw-Man Proposal.
On y retrouve bien sûr le détail des fonctionnalités de base dont j’ai déjà parlé plusieurs fois sur ce blog : les expressions Lambda, les types Fonction et les règles de conversion vers les types SAM.
D’autres sections restent vides, comme la transparence des exceptions ou les références de méthodes, mais sont bel et bien prévues dans la proposition (il s’agit toujours d’un document de travail).
Enfin, on dispose de plus d’information quand à l’accès aux variables, mais surtout on y découvre l’intégration des méthodes d’extensions !
Du coup j’en profite pour mettre à jour le tableau comparatif que j’avais publié dans un billet précédent :
CICE | BGGA 0.5 | FCM 0.5 | CFJ 0.6a | Devoxx | Lambda | |
---|---|---|---|---|---|---|
Littéraux pour la réflection | NON | NON | OUI | NON | ? | ? |
Référence de méthode | NON | NON | OUI | OUI | ? | prevu |
Closures assignable aux interfaces mono-méthode | OUI | OUI | OUI | OUI | ? | OUI |
Closures indépendante des interfaces mono-méthode | NON | OUI | OUI | OUI | OUI | OUI |
Accès aux variables locales non-final | OUI | OUI | OUI | OUI | Peut-être pas | OUI |
‘this’ référence la classe englobante | NON | OUI | OUI | OUI | Probablement | OUI |
‘return’ lié à la closure | OUI | OUI* | OUI | OUI | OUI | OUI |
‘return’ lié à la méthode appellante | NON | OUI* | NON | NON | NON | NON |
Transparence des exceptions | NON | OUI | OUI | OUI | Peut-être pas | prévu |
Type ‘Fonction’ | NON | OUI | OUI | OUI | OUI | OUI |
Structures de contrôles | NON | OUI | NON | NON | NON | NON |
Méthodes d’extension | NON | NON | NON | NON | NON | OUI |
* : La proposition BGGA 0.5 permettait au mot-clef return d’avoir deux significations différentes selon le contexte. |
Accès à l’instance ‘this’
C’est un détail d’implémentation, mais c’est confirmé : le mot-clef this référencera bien l’instance englobante et non pas l’instance de l’objet représentant l’expression lambda.
Accès aux variables locales en lecture (final implicite)
Petit rappel : actuellement dans une classe anonyme, on ne peut accéder qu’aux variables locales précédemment déclarées final, car on travaille en réalité sur une copie du type primitif ou référence de la variable.
Avec les expressions Lambda, ceci sera optionnel tant que la variable respectera bien les conditions du final. On pourra accéder à une variable tant que la variable n’est assignée qu’une seule fois avant la déclaration de l’expression lambda, et qu’elle n’est plus modifiée par la suite. En clair, on pourra y accéder tant qu’elle répond aux conditions de final, et cela revient ni plus ni moins à rendre le mot-clef final implicite : c’est le compilateur qui le mettra pour nous.
Accès aux variables locales en lecture/écriture
Toutefois la proposition ne s’arrête pas là puisqu’elle met en place un nouveau mot-clef (shared) qui permettra aux expressions lambda de partager l’accès à n’importe quelle variable locale en lecture/écriture.
L’exemple donné permet de comptabiliser le nombre de comparaisons effectuées pendant un tri :
shared int count = 0; // Déclaration de la variable partagée
Collections.sort( data,
#(String a, String b) {
count++;
return a.length() - b.length()
}
);
System.out.println(count)
La variable locale peut alors être modifiée à l’intérieur de l’expression lambda. Bien sûr, ces modifications se reportent alors dans la méthode appelante.
Pour info, contrairement à assert ou enum en leurs temps, l’ajout de ce mot-clef ne devrait pas poser de problème de compatibilité puisqu’il s’agit d’un mot-clef restreint au contexte.
Les méthodes d’extension
Lors de l’annonce du retour des closures à la conférence Devoxx, il était également question des méthodes d’extension, mais je n’avais pas vraiment fait attention à cela. Il faut dire que j’avais vu plusieurs propositions dans ce sens qui ne m’avaient pas vraiment convaincu, car l’appel d’une méthode pouvait devenir un peu imprévisible… Mais cette proposition me semble nettement plus « propre ».
Mais revenons-en à la source du problème : les interfaces ne peuvent pas être modifiées sans casser la compatibilité. En effet, la moindre modification dans une interface va forcément impacter toutes les implémentations potentielles, que ce soit dans l’API standard mais surtout dans toutes les applications existantes.
Du coup, une fois qu’une interface est utilisée, on ne peut plus y ajouter de nouvelle méthode sans casser la compatibilité, et on passe généralement via une méthode static dans une classe utilitaire, comme c’est généralement le cas avec la classe Collections pour les différentes interfaces de l’API de Collections.
Toutefois, l’utilisation de ces méthodes static est bien moins pratique qu’un appel de méthode standard. C’est là qu’interviennent les méthodes d’extension, en permettant d’appeler une méthode static comme s’il s’agissait d’une méthode d’instance.
Pour cela, il suffit d’ajouter la signature de méthode dans l’interface, en spécifiant via un import static le nom de la méthode qui sera exécutée par défaut lors de l’appel de la méthode.
Par exemple, si on souhaite modifier l’interface List pour y inclure la méthode sort(), il suffit de modifier l’interface de la manière suivante :
public interface List<T> extends Collection<T> {
...
public void sort() import static Collections.sort;
}
La méthode sort() devient de ce fait optionnelle : les classes qui implémentent l’interface List n’ont pas à l’implémenter, mais on peut l’appeler sur toutes les instances de la classe List :
List<String> list = ...
list.sort();
Par défaut un tel appel serait converti en un appel équivalent vers la méthode statique Collections.sort(), soit :
Collections.sort(list);
Toutefois, si l’implémentation réelle de l’objet « list » possède une méthode sort(), c’est cette dernière qui serait alors exécutée par la JVM. Ce qui permet de conserver un mécanisme d’héritage malgré tout. La méthode statique servant en fait d’implémentation par défaut.
L’idée me semble vraiment intéressante, car cela permettra enfin de faire évoluer les sacro-saintes interfaces sans tout casser, tout en restant assez restreinte pour éviter les dérives. En clair cela reste à la charge du concepteur de l’interface et non pas du premier développeur venu, ce qui évitera les mauvaises utilisations, les abus et les confusions…
Reste à voir concrètement ce que cela va donner, et comment ce sera utilisé dans l’API standard.
13 Commentaires + Ajouter un commentaire
Tutoriels
Discussions
- Recuperation du nom des parametres
- [REFLEXION] Connaitre toutes les classes qui implémentent une interface
- Classes, méthodes private
- Possibilité d'accéder au type générique en runtime
- L'apparition du mot-clé const est-il prévu dans une version à venir du JDK?
- Difference de performances Unix/Windows d'un programme?
- jre 1.5, tomcat 6.0 et multi processeurs
- [ fuite ] memoire
- Définition exacte de @Override
prof: c’est en effet un problème.
Il y a eu plusieurs discussions sur le sujet dans la mailing list, mais le premier brouillon de la spécification n’en parlait pas…
Il y avait beaucoup de pour et de contre… surtout que contrairement à ce que je pensait lorsque j’ai rédigé ce billet, il ne devrait pas y avoir d’héritage ou d’équivalent.
Certains ont suggéré une nouvelle syntaxe pour bien les différencier des autres méthodes.
D’autres préfèrerait voir débarquer les Traits de Scala (des interfaces contenant des implémentations), même si ce n’est pas non plus sans poser problème.
Sinon un problème similaire se pose problème se pose avec les expressions lambda :
correspond-il à un appel de méthode ou à l’utilisation d’une expression lambda ?
D’après le premier brouillon cela devrait être résolu via une nouvelle syntaxe d’appel :
correspondrait à l’appel de la méthode
de la manière la plus classique qu’il soit.
permettrait d’utiliser l’expression lambda « hello ».
Mais bon il y a beaucoup de discussions et d’idées en tout genre sur la mailing list ce qui la rend assez difficile à suivre. J’attends de voir le second brouillon de la spec…
a++
à titre d’expérience j’ai manipé les méthodes d’extension en C# (eh oui ;-))
ma conclusion: j’aime pas du tout! pourquoi? parceque, finalement, c’est une forme de macro, avec tous les inconvénients qui font que les macros ont été déclarées personna non grata en Java. Dans le code qui utilise cette facilité on ne sait pas, à première lecture, quelle est le code qu’on appelle.
Il peut y avoir conflit entre la méthode d’instance « f » dans la classe « A » et la méthode d’extension correspondante, que doit faire le compilateur? ce n’est pas clair, clair; mais là où les choses se corsent c’est quand on définit une méthode d’extension « f » pour la classe « A » et qu’ensuite, manque de chance, la nouvelle version de la classe « A » définit une méthode de même nom! Alors là (en C# du moins) les choses se gatent (bien sûr c’est déjà le cas avec des méthodes normales mais là, au moins, on est averti).
finalement j’aime pas du tout cette idée … mais je me réserve le droit de changer d’avis (si on trouve un argument convaincant à l’utilité des ces méthodes).
Thanks for reading !
For BGGA I want to see that there are two alternative : the keyword « return » can be used to return from a method, and the last value of a closure was the return value…
But you’re right, this is not very understandable on my text !
For the lambda proposal, I’m afraid that I misunderstood the proposal. I will correct this in a future ticket…
Thanks for reading
Your chart is incorrect. In BGGA, « return » only ever returns from the enclosing method, and never from the closure.
In the project Lambda proposal, on the other hand, « return » can be used to return from a method (as it can today), or from a closure, depending on which is nearest (in other words, depending on context).
plus j’y refléchi et plus je pense que mon exemple précis pose problème.
en effet equals est défini pour Object et donc quand on définit une classe
qui implante un interface disposant d’une méthode d’extension quelle est la méthode prioritaire?
Celle héritée (de Object en l’occurence) ou celle venue de l’interface? Toutes les chances que celle de Object soit prioritaire! un dispositif qui contrarierai explicitement cette prédisposition serait vraiment bienvenu!
(le « contrat » de l’interface serait ainsi conservé, sauf spécialisation explicite)
De plus l’appel de la méthode d’extension dans une spécialisation va se compliquer méchament avec ce type de conflit….
J’ai parcouru un peu les mailling-list, mais j’avoue avoir eu du mal à avoir un réponse précise… donc j’attends d’avoir des infos plus concrète (une proposition plus abouti ? une implementation ?)
a++
suite du commentaire précédent: ce qui me chiffonne dans la règle c’est que j’aimerais savoir comment écrire dans une méthode « masquante » un appel à la méthode d’origine (comme quand on fait super.methode() sauf qu’ici ce n’est pas le cas). quel est le statut de cette méthode définie avec static import? est ce quelque chose appelable par List.equals(autre) pseudo-méthode statique de l’interface List?
Si on reprend la terminologie de JLS pour « hidden » c’est la deuxième hypothèse qui me semble la bonne. C’est un masquage de compile time
Oui cela ressemble beaucoup aux Traits…
Mais en fait la proposition n’est pas très précise et le fonctionnement exacte de tous les éléments n’est pas très clair. En particulier cette phrase qui n’est pas très clair à mon avis :
Que signifie « hidden » ici ?
Bref en fait il faudrait attendre d’avoir plus de précision…
a++
je suis en train de me demander si les méthodes d’extension ne permettrait pas d’introduire une certaines forme de « trait » en passant par une ruse:
soit par exemple une méthode statique
public static boolean listEquals(List list, Object equals) {<br />
// l'égalité des listes est spécifiée de manière particulière<br />
}<br />
serait-il possible de faire ce hack de la mort?
public interface List {<br />
<br />
public boolean equals(Object other) import static ListUtils.listEquals() ;<br />
// ou queque chose comme ça avec la syntaxe qu'il faut ...<br />
}<br />
@Baptiste : on ne pourrait pas modifier nous même les interfaces de l’API de base.
C’est justement l’intérêt de la chose par rapport aux autres propositions, car cela éviteras les extensions dans tous les sens qui risque de rendre le code incompréhensible en première lecture…
Après c’est sûr mais cela reste plus limité… mais moins dangereux !
a++
Excellente nouvelle que ces méthodes d’extension, mais comment cela va se passer pour étendre les interfaces de l’API de base ?
Est-ce qu’il y a aura moyen d’ajouter des méthodes d’extension à l’interface List nous même ? Et est-ce qu’il faudra pour cela créer un package java.util ou il y a aura des facilités d’extension ?
C’est vrai que cette proposition d’évolution me plait beaucoup, contrairement à celles que j’avais vues jusque là (particulièrement dans le sondages posté il y a quelque temps sur le forum):
– Ça peut en effet limiter les dérives.
– Mais surtout ca donnera une visibilité à ces méthode bien meilleures.
En effet, si j’ai bien compris, ces méthodes seront immédiatement disponibles pour l’utilisateur. Il n’aura pas besoin de faire « import static » d’une classe qu’il doit préalablement connaitre.
J’espère d’ailleurs que la javadoc sera adaptée pour rendre compte des méthodes d’extension(en faisant quand même la distinction avec les méthodes classiques)
Avant Devoxx, j’étais très déçu par le manque de nouveautés du JDK 7, mais finalement ça s’annonce vraiment pas mal. A par les structures de contrôle et une vraie gestion des propriétés dans le langages, il ne manque plus grand chose de ce que j’espérais.