Java 8 : Le Projet Lambda

La loi de Moore se traduit aujourd’hui, non par la puissance des processeurs, mais par la multiplication de leurs nombres. On vit depuis un peu plus d’une décennie l’ère des processeurs multi-cœur. Exploiter le mieux possible cette puissance de calcul – par la parallélisation des processus – reste un enjeu majeur de l’ingénierie logicielle. Actuellement des API existent en Java mais leur caractère relève d’un vrai jardin du diable. La complexité de leur implémentation, couplée à la sphère de l’interblocage, limite leur utilisation dans les programmes informatiques. Alors que la plus part des problématiques de parallélisation apparaissent au cours du développement, vu la profondeur du gap qui existe entre un programme classique (séquentiel) et un programme parallèle, le choix de paralléliser (ou non) doit être pris en compte depuis la phase la conception.

Brian Goetz est architecte Java chez Oracle et l’auteur de « Programmation concurrente en Java ». Dans les premières pages de son livre il donne des chiffres intéressants sur le nombre de bugs dévoilés sur des applications, conçues pour des architectures traditionnelles, lors de leur premier déploiement sur une architecture parallèle. En effet, même si nos programmes ne prennent pas en charge cette problématique de la parallélisation, on s’appuie régulièrement sur des librairies qui font du multithreading, surtout dans le développement web.

Le Projet Lambda

Un petit clin d’œil à Stéphane qui a consacré un billet sur le futur de Java, il y a déjà deux ans, avec une illustration en PHP. 😉

L’objectif principal du projet lambda est, en s’appuyant sur la puissance de la programmation fonctionnelle, de prendre le taureau par les cornes en rendant la construction de programmes parallèles aussi simple que possible et donner par la même occasion une syntaxe plus funky. A cet effet, tous les composants de l’écosystème Java, le noyau du langage, la machine virtuelle et les librairies de base, sont appelés à évoluer.

La programmation fonctionnelle

Tout comme la programmation impérative, la programmation objet, la programmation logique et les systèmes multi-agent, la programmation fonctionnelle est un autre paradigme de programmation. A l’origine elle est fondée sur la notion mathématique de fonction ; une application qui associe à chaque élément d’un ensemble A dit Ensemble de Départ au plus un élément d’un ensemble B dit Ensemble d’Arrivée.

Ceci n’est pas une fonction :

int i = 0 ;
int f ( int j ) {
i = i + j ;
return i ;
}

La valeur de f(j) ne dépend pas seulement de j, mais aussi de i. Pour une même valeur de j on peut avoir deux résultats différents de f(j). Donc f n’est pas une fonction. Voici les conditions qu’elle doit satisfaire pour en être une :

  1. f ne doit dépendre d’aucune variable en dehors de son contexte
  2. Aucune variable de f ne peut être modifiée en dehors de son contexte
  3. Aucune variable de f ne peut conserver son état

Et tout ça pour en venir où ? En fait une des tâches du compilateur consiste à chercher les dépendances entre les différentes instructions afin de paralléliser celles qui ne présentent pas de dépendance. La syntaxe fonctionnelle introduit moins de dépendances – les fonctions sont dites closes- et donne suffisamment d’informations au compilateur pour lui faciliter l’optimisation du code.

Les lambda expressions

Longtemps cantonnés dans les laboratoires de recherche et réservée aux universitaires, les Lambda expressions (techniquement aussi appelés « closure ») sont enfin disponibles en Java. En réalité ceci n’a rien de révolutionnaire, tous les langages concurrents de Java, C#, Scala et Groovy (et smalltalk depuis plus de 30 ans) permettent d’utiliser les Lambda expressions. Au début c’était attendu pour Java 7 avec le projet Coin mais leur intégration a dû être reportée pour des contraintes de délai. Le projet est actuellement en cours de finalisation et la première release est prévue pour début 2013.

De quoi s’agit-il exactement ? Les Lamba expressions sont des fonctions anonymes qui sont à la base de la programmation fonctionnelle. Elles ont la même structure qu’une méthode ; une liste d’arguments, un type de retour, un corps et les exceptions à lever.

(type parameter) -> function_body

Quelques exemples de lambda expression :

(int x) -> x + 1 ;
(int x, int y) -> x + y ;
(int x, int y) -> { System.out.println(" Résulat : " + (x+y)); }
() -> { System.out.println("I am a lambda expression"); }

Les types des paramètres ne sont pas obligatoires, ils peuvent être obtenus par inférence, déduits en fonction du contexte.

Exemple :

(x) -> x + 1 ;
(x, y) -> x + y ;
(x, y) -> { System.out.println(" Résulat : " + (x+y)); }

Les expressions lambda peuvent être stockée dans des variables, passées en paramètre ou retournée par des méthodes.
Exemple:

X = () -> {while (true) { System.out.println("Hello"); }}
new Thread(X).start();

Les classes anonymes de Java

Brian Goetz rappelle que des structures dont le fonctionnement se rapproche des closures existent déjà en Java, les fameuses classes anonymes, avec toutefois une légère différence sur la capture des variables déclarées avec final. Couramment utilisés pour le traitement des évènements des IHM, elles sont à l’origine de sucre syntaxique assez inélégant.

Exemple :

button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
//…
}
}

Ce qui pourrait s’écrire avec les expressions lambda :

button.addActionListener(e -> {…})

Les lambda expression et les collections Java

Les lambda expressions permettront également de simplifier la manipulation des collections à travers les trois opérations d’agrégation incontournables de la programmation fonctionnelle, le map, le filter et reduce (ou fold). En PF ce sont des fonctions d’ordre supérieure, elles prennent en paramètre une fonction et une collection, et peuvent retourner une fonction. En POO, ce seront de nouvelles méthodes de la classe Collections.

Exemple :
Map : Applique sur les éléments d’une collection une fonction et retourne une liste.

List counts = new ArrayList();
for(int i=1;i <= 10; i++){
  counts.add(i);
}
aList = counts.map(i -> (i*2));

Filter : Applique sur les éléments d’une collection un prédicat et retourne une collection composée des éléments qui vérifient le prédicat.

List filteredList = …
for ( Integer i : counts){
if ( i % 2 == 0 ){
filteredList.add(i);
}
}
aList = counts.filter(i -> i%2 == 0);

Reduce : Applique sur deux éléments consécutifs d’une collection une fonction qui retourne une valeur. On peut l’utiliser pour faire la somme des éléments d’une liste, trouver le plus grand élément d’une liste comme dans cet exemple, …

int max = 0;
for ( Integer i : counts){
if ( i > max ){
max = i;
}
}
max = counts.reduce(0, (x,y)-> x>y);

Conclusion

J’ai essayé de montrer à travers ce billet l’intérêt du projet Lambda dans le contexte actuel de l’ingénierie logicielle et de faire découvrir à travers des exemples jusqu’à niveau la syntaxe du langage va évoluer, on peut par exemple prévoir la disparition des classes anonymes. Dans un prochain billet je parlerai d’autres aspects du projet, notamment les VEM et les SAM.

Références

Brian Goetz : http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-4.html
M.V. Aponte : Introduction à la programmation fonctionnelle avec Ocaml
Urs Peter and Sander van den Berg : www.infoq.com/articles/java-8-vs-scala
Blog Oxiane2010/12/01 les lambda fonctions en php ou comment en arriver à discuter du futur de java (épisode 1)