Syndication : Atom 1.0  RSS 2.0
Blogs des développeurs   »   adiGuba:Blog

Article complet: Les expressions lambda (closures) se dotent de méthodes d'extension !

11/12/2009

Permalink 15:03:11, Catégories: Java, 7 (Dolphin), Récapitulatif Java, Closure/Lambda, 1159 mots   French (FR) , adiGuba

[Java] Les expressions lambda (closures) se dotent de méthodes d'extension !


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.

[Suite:]

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.

Social Bookmarking:

                                     

Adresse de trackback pour cet article:

http://blog.developpez.com/htsrv/trackback.php?tb_id=8451

Commentaires, Trackbacks, Pingbacks:

Connectez-vous pour vous abonner à cet article:

Flux de commentaires pour cet article : Atom 1.0  RSS 2.0
Commentaire de: Uther [Membre]
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.
Permalien 11/12/2009 @ 19:16
Commentaire de: Baptiste Wicht [Membre] · http://baptiste-wicht.developpez.com
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 ?
Permalien 12/12/2009 @ 10:31
Commentaire de: adiGuba [Membre] · http://adiguba.developpez.com
@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++
Permalien 12/12/2009 @ 11:00
Commentaire de: professeur shadoko [Membre]
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) {
// l'égalité des listes est spécifiée de manière particulière
}

serait-il possible de faire ce hack de la mort?

public interface List {

public boolean equals(Object other) import static ListUtils.listEquals() ;
// ou queque chose comme ça avec la syntaxe qu'il faut ...
}


Permalien 16/12/2009 @ 11:54
Commentaire de: adiGuba [Membre] · http://adiguba.developpez.com
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 :
An extension method in an interface is hidden in a class implementing that interface if the class defines a static or instance method with the same signature.


Que signifie "hidden" ici ?
  • Qu'on a un mécanisme similaire aux Traits avec un "faux" héritage comme je le pensais lorsque j'ai écrit le billet. Mais dans ce cas il s'agirait de modifier le mode d'invocation, et donc modifier le bytecode !!!
  • Ou simplement que la méthode static serait invisible, et donc on ne peut pas vraiment parler d'héritage car le choix de la méthode se ferait uniquement lors de la compilation...



Bref en fait il faudrait attendre d'avoir plus de précision...

a++
Permalien 16/12/2009 @ 12:15
Commentaire de: professeur shadoko [Membre]
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

Permalien 16/12/2009 @ 12:23
Commentaire de: professeur shadoko [Membre]
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?
Permalien 16/12/2009 @ 12:33
Commentaire de: adiGuba [Membre] · http://adiguba.developpez.com
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++
Permalien 16/12/2009 @ 15:25
Commentaire de: professeur shadoko [Membre]
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....

Permalien 16/12/2009 @ 16:58
Commentaire de: nmgafter [Membre]
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).
Permalien 29/12/2009 @ 00:15
Commentaire de: adiGuba [Membre] · http://adiguba.developpez.com
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 ;)

Permalien 29/12/2009 @ 10:45
Commentaire de: professeur shadoko [Membre]
à 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).
Permalien 08/02/2010 @ 12:26
Commentaire de: adiGuba [Membre] · http://adiguba.developpez.com
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 : hello() 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 :
  • hello() correspondrait à l'appel de la méthode hello() de la manière la plus classique qu'il soit.
  • hello!() 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++
Permalien 08/02/2010 @ 17:29

Vous devez être identifié pour poster un commentaire.

Liste des blogs

Rechercher

<  Novembre 2011  >
Lun Mar Mer Jeu Ven Sam Dim
  1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30        

Syndiquez ce blog XML

Articles :

Commentaires :

 
 
 
 
Partenaires

Hébergement Web