Raisons de la suppression des types de fonctions dans Java 8

12

J'ai essayé de comprendre pourquoi le JDK 8 Lambda Expert Group (EG) a décidé de ne pas inclure un nouveau type de fonction dans le langage de programmation Java.

En parcourant la liste de diffusion, j'ai trouvé un fil avec la discussion sur la suppression des types de fonctions .

Beaucoup de déclarations sont ambiguës pour moi, peut-être en raison du manque de contexte et dans certains cas en raison de ma connaissance limitée de la mise en œuvre des systèmes de type.

Cependant, il y a quelques questions que je crois pouvoir formuler en toute sécurité dans ce site pour m'aider à mieux comprendre ce qu'elles signifiaient.

Je sais que je pourrais poser les questions dans la liste de diffusion, mais le fil est ancien et toutes les décisions sont déjà prises, donc il y a des chances que je sois ignoré, surtout vu que ces gars sont déjà en retard avec leurs plans.

Dans sa réponse pour soutenir la suppression des types de fonctions et approuver l'utilisation des types SAM, Brian Goetz dit:

Pas de réification. Il y avait un long fil sur l'utilité de réifier les types de fonctions. Sans réification, les types de fonction sont entravés.

Je n'ai pas pu trouver le fil qu'il mentionne. Maintenant, je peux comprendre que l'introduction d'un type de fonction structurelle peut impliquer certaines complications dans le système de type Java principalement nominal, ce que je ne peux pas comprendre, c'est comment les types SAM paramétrés sont différents en termes de réification.

Ne sont-ils pas tous les deux soumis aux mêmes problèmes de réification? Est-ce que quelqu'un comprend en quoi les types de fonctions sont différents des types SAM paramétrés en termes de réification?

Dans un autre commentaire, Goetz dit:

Il existe deux approches de base du typage: nominal et structurel. L'identité d'un nominal est basée sur son nom; l'identité d'un type structurel est basée sur ce qui le compose (comme «tuple d'int, int» ou «fonction d'int à flottant».) La plupart des langues choisissent principalement nominale ou principalement structurelle; il n'y a pas beaucoup de langues qui mélangent avec succès le typage nominal et structurel, sauf "autour des bords". Java est presque entièrement nominal (à quelques exceptions près: les tableaux sont un type structurel, mais en bas, il y a toujours un type d'élément nominal; les génériques ont également un mélange de nominal et structurel, et cela fait en fait partie de la source de nombreux des plaintes des gens sur les génériques.) Greffage d'un système de type structurel (types de fonctions) sur Java ' Le système de type nominal signifie de nouveaux cas de complexité et de bord. Les avantages des types de fonction en valent-ils la peine?

Ceux d'entre vous ayant une expérience dans la mise en œuvre de systèmes de type. Connaissez-vous des exemples de ces complexités ou cas marginaux qu'il mentionne ici?

Honnêtement, je suis confus par ces allégations quand je considère qu'un langage de programmation comme Scala, qui est entièrement basé sur la JVM, prend en charge les types structurels comme les fonctions et les tuples, même avec les problèmes de réification de la plate-forme sous-jacente.

Ne vous méprenez pas, je ne dis pas qu'un type de fonction devrait être meilleur que les types SAM. Je veux juste comprendre pourquoi ils ont pris cette décision.

edalorzo
la source
4
"Greffer un système de type structurel (types de fonctions) sur le système de type nominal de Java signifie une nouvelle complexité" - cela était très probablement destiné à des complexités pour les utilisateurs qui doivent en savoir plus pour utiliser le langage . Quelque chose comme Java simple et facile à utiliser devient complexe et difficile à apprendre en C ++ , des trucs comme ça. Les membres de la liste de diffusion pourraient probablement faire allusion à la critique d'une "grande vague d'améliorations" antérieure, un exemple frappant étant Java 5 suce un article de Clinton Begin de MyBatis
gnat
3
"Java simple et facile à utiliser devient comme C ++ complexe et difficile à apprendre": Malheureusement, un problème avec les langages de programmation traditionnels est qu'ils doivent conserver la compatibilité descendante et donc ils ont tendance à devenir trop complexes. Donc, à mon humble avis (ayant de nombreuses années d'expérience avec C ++ et Java), il y a quelque chose de vrai dans cette déclaration. J'ai souvent l'impression que Java devrait être gelé et qu'un nouveau langage devrait être développé à la place. Mais Oracle ne peut pas prendre un tel risque.
Giorgio
4
@edalorzo: Comme l'ont montré Clojure, Groovy, Scala et d'autres langages, il est possible (1) de définir un nouveau langage (2) d'utiliser toute la richesse des bibliothèques et des outils déjà écrits en Java sans rompre la compatibilité descendante (3) donner Les programmeurs Java ont la liberté de choisir s'ils veulent rester avec Java, passer à un autre langage ou utiliser les deux. IMO, étendre indéfiniment une langue existante est une stratégie pour garder les clients, de la même manière que les entreprises de téléphonie mobile doivent créer de nouveaux modèles à tout moment.
Giorgio
2
Comme je l'ai compris de SAMbdas en Java , l'une des raisons est qu'il serait problématique de garder certaines bibliothèques (collections) rétrocompatibles.
Petr Pudlák
2
Article connexe sur SO qui renvoie à cet autre article de Goetz .
assylias

Réponses:

6

L'approche SAM est en fait quelque peu similaire à ce que Scala (et C ++ 11) font avec les fonctions anonymes (créées avec l' =>opérateur de Scala ou la syntaxe C ++ 11 []()(lambda)).

La première question à laquelle il faut répondre du côté de Java est de savoir si le type de retour d'une instruction lambda doit être un nouveau type primitve, comme intou byte, ou un type d'objet quelconque. Dans Scala, il n'y a pas de types primitifs - même un nombre entier est un objet de classe Int- et les fonctions ne sont pas différents objets étant de la classe Function1, Function2et ainsi de suite, selon le nombre d'arguments de la fonction prend.

C ++ 11, ruby ​​et python ont de même des expressions lambda qui retournent un objet qui peut être appelé de manière explicite ou implicite. L'objet renvoyé possède une méthode standard (telle que celle #callqui peut être utilisée pour l'appeler en tant que fonction. C ++ 11, par exemple, utilise un std::functiontype qui surcharge operator()afin que les appels de la méthode d'appel de l'objet ressemblent même textuellement à des appels de fonction. :-)

Là où la nouvelle proposition pour Java devient désordonnée, c'est dans l'utilisation du typage structurel pour permettre d'assigner une telle méthode à un autre objet, comme un Comparatorqui a une seule méthode principale avec un nom différent . Bien que ce soit sur le plan conceptuel Icky d' une certaine façon, il ne signifie pas que l'objet résultant peut être transmis à des fonctions existantes qui prennent un objet pour représenter un rappel, un comparateur, et ainsi de suite, et attendent de pouvoir appeler un seul bien défini telle que #compareou #callback. L'astuce de C ++ operator()consistant à ignorer soigneusement ce problème avant même C ++ 11, car toutes ces options de rappel étaient appelables de la même manière, et donc la STL ne nécessitait aucun ajustement pour permettre aux lambdas C ++ 11 d'être utilisés avecsortetc. Java, n'ayant pas utilisé de dénomination standard pour de tels objets dans le passé (peut-être parce qu'aucune astuce comme la surcharge des opérateurs ne rendait une approche unique évidente), n'a pas autant de chance, donc ce hack les empêche d'avoir à changer beaucoup d'API existantes .

Jimwise
la source
1
Ironiquement, le problème d'avoir une myriade de types différents et incompatibles, tous représentant une sorte de "chose semblable à une fonction" aurait pu être évité en ajoutant des types de fonction standard à la bibliothèque dès le début, indépendamment de la syntaxe littérale réelle pour les construire. S'il y avait eu java.util.Function2<T1, T2, R>dans Java 1.0, il n'y aurait pas d' Comparatorinterface, mais toutes ces méthodes prendraient a Function2<T1, T2, int>. (Eh bien, il y aurait eu toute une série d'interfaces comme à Function2TT_int<T1, T2>cause des primitives, mais vous comprenez mon point de vue.)
Jörg W Mittag