09 octobre 2012

The road to Lambda

Brian Goetz, que vous connaissez peut-être pour le livre « concurrency in java », présente l’historique et le rôle des lambda qui apparaitrons en java 8.

Java est le dernier langage majeur à ne pas disposer de « closures » - même C++ en a dans sa dernière mise à jour ! Si le concept pouvait paraître trop avancé à l’époque de java 1.0, qui visait à proposer une plateforme simple et accessible (comparée à C/C++), les temps ont changé et Java se doit de s’équiper de ce bagage devenu indispensable, comme nous allons le voir.


Une lambda est grosso-modo une méthode anonyme qui peut accéder au contexte lexical depuis lequel on l’invoque. La syntaxe a longtemps été débattue, et je ne suis pas super fan de celle que a été retenue - j’aimais bien #(params){ … } - mais comme pour le reste ce n’est qu’une question d’habitude.

Pour mettre en évidence le rôle des lambda, Brian part de l’exemple du traitement d’une collection par itération. Le foreach traditionnel, dit « itération externe », oblige le code client à interagir lourdement avec la « librairie » (java.util.collections). L’iterateur et ses méthodes hasNext() / next() montre d’ailleurs un soucis d’implémentation avec une inévitable duplication de code. Les lambda, bien au dela d’un simple changement de syntaxe, permettent de passer à une « itération interne », où on laisser à la librairie le soin de traiter l’itération et d’invoquer le code client au bon moment (un peu comme avec le pattern visitor).

Le traitement en parallèle d’un collection devient alors très simple à mettre en œuvre, en exposant simplement un mécanisme de Stream, et une approche fonctionnelle du code, avec des appels chainés de méthodes de filtrage, mapping ou aggregation, plutôt qu’impérative - il va falloir ressortir vos cours de Lisp :P. Ce petit pas vers la programmation fonctionnelle pourra aussi vous aider à mieux appréhender des langages fonctionnels comme scala et clojure, et donc à élargir votre panel de compétences.

Cette possibilité de prendre en charge le parallélisme du hardware avec un moindre impact sur le code est le principal argument qui a poussé la mise en œuvre des lambdas, afin de permettre à java de bénéficier de la multiplication des cœurs. Le « fork-join » de java 7 est en effet encore bien trop complexe, verbeux et différent d’une itération classique pour être significativement adopté en dehors de code très critique. Les lambdas ne suffisent cependant pas pour permettre à java de devenir un bon language multi-cœur, car il faut faire avec l’héritage en place, à savoir l’API collections.

L’évolution de JDBC a montré que l’ajout de nouvelles méthodes dans une interface est une erreur catastrophique (regardez le build de commons-dbcp pour vous en convaincre). Aussi les default methods ouvrent la voie à de nouveaux usages sans briser la compatibilité. Lambda et default methods ont donc été introduites dans Java 8 avec un objectif bien précis, et leur impact général est encore délicat à estimer.

Les default methods ont fait penser à l’arrivée de l’héritage multiple en java. Techniquement parlant cet héritage existe déjà, par contre il est restreint et ne permet pas l’héritage d’état. Cela s’apparente aux extensions method de C# pour ceux qui connaissent. En case de conflit sur une default method, si j’implémente Foo et Bar qui définissent toutes deux la default method blah(), le compilateur réclamera une redéfinition explicite. Ce mécanisme est donc prévisible et mentalement acceptable, ce qui n’est pas forcément le cas de l’héritage multiple dans d’autres langages qui nécessitent un esprit bien entrainé.

Cette session repositionne donc l'introduction des Lambda et des default methods dans java 8, avec un objectif précis au départ, et un potentiel évident sur de nombreux autres domaines. Il est intéressant de voir que ces évolutions sont parties d'un besoin spécifique, et pas juste d'une volonté d'apporter un nouvel élément de syntaxe à Java.