59
Un ch'ti peu de lambda des lambdas à l'ombre du beffroi Rémi Forax Décembre 2012

Java 8 : Un ch'ti peu de lambda

Embed Size (px)

DESCRIPTION

Slides de la session du Ch'ti JUG animée par Remi Foxax sur Java 8

Citation preview

Page 1: Java 8 : Un ch'ti peu de lambda

Un ch'ti peu de lambdades lambdas à l'ombre du beffroi

Rémi ForaxDécembre 2012

Page 2: Java 8 : Un ch'ti peu de lambda

Moi

MCF à l'université Paris Est Marne-La-Vallée

Joue avec Java depuis trop longtemp pour l'avouer

Créateur de langage dynamique ou pas

Expert pour les JSR 292 (invokedynamic) et JSR 335 (lambda)

Contributeur OpenJDK, ASM, Tatoo, PHP.reboot, JDart, etc...

Page 3: Java 8 : Un ch'ti peu de lambda

Les lambdaaaaahs

Pourquoi

par ce que Java c'était trop simple ??

Comment

on va tout casser Java ??

Sous le capot

surtout ne pas toucher à la VM ??

Page 4: Java 8 : Un ch'ti peu de lambda

Partie IIntroduction aux lambdas

Page 5: Java 8 : Un ch'ti peu de lambda

Dit papa, c'est quoi des lambdas ?

private static ArrayList<File> subDirectories(File directory) { ArrayList<File> directories = new ArrayList<>(); File[] files = directory.listFiles(); for(File file: files) { if (file.isDirectory()) { directories.add(file); } } return directories;}

public static void main(String[] args) { File file = new File("."); for(File dir: subDirectories(file)) { System.out.println(dir); }}

Page 6: Java 8 : Un ch'ti peu de lambda

Parties codantes

private static ArrayList<File> subDirectories(File directory) { ArrayList<File> directories = new ArrayList<>(); File[] files = directory.listFiles(); for(File file: files) { if (file.isDirectory()) { directories.add(file); } } return directories;}

public static void main(String[] args) { File file = new File("."); for(File dir: subDirectories(file)) { System.out.println(dir); }}

Page 7: Java 8 : Un ch'ti peu de lambda

Seconde version

private static File[] subDirectories(File directory) { return file.listFiles(new FileFilter() { @Override public boolean accept(File path) { return path.isDirectory(); } });}

public static void main(String[] args) { File file = new File("."); for(File dir: subDirectories(file)) { System.out.println(dir); }}

Page 8: Java 8 : Un ch'ti peu de lambda

Parties codantes

private static File[] subDirectories(File directory) { return file.listFiles(new FileFilter() { @Override public boolean accept(File path) { return path.isDirectory(); } });}

public static void main(String[] args) { File file = new File("."); for(File dir: subDirectories(file)) { System.out.println(dir); }}

Page 9: Java 8 : Un ch'ti peu de lambda

Problème des classes anonymes

Une classe anonyme est pas super adaptée

visuellement

Verbeux, rapport signal/bruit pas satisfaisant

sémantiquement

On veut envoyer une expression, créont une classe ...

performancecréation d'un objet à chaque appel

+1 classe sur le disque

+1 instance de java.lang.Class + métadata en mémoire

Page 10: Java 8 : Un ch'ti peu de lambda

Comment font les autres langages ?

Lisp/Clojure closure's

Ruby block

Groovy Closure

Scala/C# lambda

Javascript/Python anonymous function

Python comprehension/C# Linq

Pourquoi introduire les lambdas en Java ?

les classes anonymes couvrent déjà le besoin ??

Page 11: Java 8 : Un ch'ti peu de lambda

L'excuse multicore

On a plein de coeurs et on sait pas quoi en faire !

Si une partie du code est transformable en objet

On peut distribuer / paralleliser l'exécution

Presque magique

Enfin, si on a un effet de bord, on est mort !

Page 12: Java 8 : Un ch'ti peu de lambda

Sans lambda ...

private static File[] subDirectories(File directory) { return file.listFiles(new FileFilter() { @Override public boolean accept(File path) { return path.isDirectory(); } });}

public static void main(String[] args) { File file = new File("."); for(File dir: subDirectories(file)) { System.out.println(dir); }}

Page 13: Java 8 : Un ch'ti peu de lambda

Avec une lambda ...

private static File[] subDirectories(File directory) { FileFilter filter = (File path) -> path.isDirectory(); return file.listFiles(filter);}

public static void main(String[] args) { File file = new File("."); for(File dir: subDirectories(file)) { System.out.println(dir); }}

Page 14: Java 8 : Un ch'ti peu de lambda

Syntaxe pour les expressions

sans paramètre

() -> System.out.println("welcome to the land of shtis")

avec un paramètre (inférence)

employee -> employee.isManager()

avec plusieurs paramètres

en déclarant les types

(int x, int y) -> x == y

sans déclarer les types (inférence)

(x, y) -> x == y

Page 15: Java 8 : Un ch'ti peu de lambda

Syntaxe pour les instructions

sans paramètre

() -> { System.out.println("welcome to the land of shtis");}

avec un paramètre (inférence)

employee -> { return employee.isManager();}

avec plusieurs paramètres (inférence)

(index1, index2) -> { list.set(index1, list.get(index2));}

Page 16: Java 8 : Un ch'ti peu de lambda

Sémantique

Typé par une functional interface (ex SAM)

Runnable, Callable, Filter, Function, ...

Une lambda n'est pas un objet

“this” représente la classe courante pas la lambda

Une lambda est convertissable en un objet qui implante une functional interface

Page 17: Java 8 : Un ch'ti peu de lambda

En utilisant l'inférence

private static File[] subDirectories(File directory) { return file.listFiles( path -> path.isDirectory() );}

public static void main(String[] args) { File file = new File("."); for(File dir: subDirectories(file)) { System.out.println(dir); }}

Page 18: Java 8 : Un ch'ti peu de lambda

Comment l'inférence marche ?

le compilo regarde le(s) type(s) target(s)

FileFilter filter = path -> path.isDirectory();

l'interface FileFilter est déclarée

interface FileFilter { boolean accept(File file);}

utilise jamais le body de la lambda !

Java != Haskell

Page 19: Java 8 : Un ch'ti peu de lambda

Method Reference

private static File[] subDirectories(File directory) { return file.listFiles( File::isDirectory );}

public static void main(String[] args) { File file = new File("."); for(File dir: subDirectories(file)) { System.out.println(dir); }}

Page 20: Java 8 : Un ch'ti peu de lambda

Method Reference

Raccourçi si la lambda délègue juste à une méthode

On utilise :: rien que pour embéter les C++eux

Si la méthode est surchargée, l'inférence utilise le target type pour trouver les types des paramétres

Page 21: Java 8 : Un ch'ti peu de lambda

Et les collections

Avoir des lambdas c'est bien, mais sans support au niveau des APIs ...

Nouvelle interface java.util.Stream

Sequentielle ou parallele

Operations

intermédiairefilter, map, sorted, distinct, flatMap ...

terminalesreduce, forEach, into, findFirst ...

Page 22: Java 8 : Un ch'ti peu de lambda

Full Stream

private static ArrayList<File> subDirectories( File directory) { return Arrays.stream(directory.listFiles()). filter(File::isDirectory). into(new ArrayList<>());}

public static void main(String[] args) { File file = new File("."); for(File dir: subDirectories(file)) { System.out.println(dir); }}

Page 23: Java 8 : Un ch'ti peu de lambda

Avec tout dans le main()

La méthode subdirectories() sert pas vraiment !

public static void main(String[] args) { File directory = new File("."); Arrays.stream(directory.listFiles()). filter(File::isDirectory). forEach(dir -> System.out.println(dir));}

Page 24: Java 8 : Un ch'ti peu de lambda

Capturer la valeur de variables locales

Et si je veux tous les sous-répertoires dont le nom commence par args[0]

public static void main(String[] args) { File directory = new File("."); String name = args[0]; Arrays.stream(directory.listFiles()). filter(File::isDirectory). filter(dir -> dir.getName().startsWith(name). forEach(dir -> System.out.println(dir));}

Une lambda peut capturer les valeurs des variables locales (comme avec une classe anonyme)

Page 25: Java 8 : Un ch'ti peu de lambda

Pipeline d'élements

Arrays.stream(directory.listFiles()). filter(File::isDirectory). filter(dir -> dir.getName().startsWith(name). forEach(dir -> System.out.println(dir));

filter

Arrays.asStream(array)

collection.stream()

iterator.stream()

block

On pousse les élements à travers le pipeline

filter

false false

Page 26: Java 8 : Un ch'ti peu de lambda

Method reference sur une instance

Il est possible de créer une méthode réference sur une instance

public static void main(String[] args) { File directory = new File("."); String name = args[0]; Arrays.stream(directory.listFiles()). filter(File::isDirectory). filter(path -> path.getName().startsWith(name). forEach(System.out::println);}

Page 27: Java 8 : Un ch'ti peu de lambda

En résumé

Java permet de définir des lambdas et des références sur des méthodes

Une lambda est une expression ou une fonction anonyme que l'on peut convertir en un objet pour envoyer à une méthode

L'API des collections est mis à jour pour supporter les lambdas

Page 28: Java 8 : Un ch'ti peu de lambda

Partie IIChangements pour Java

Page 29: Java 8 : Un ch'ti peu de lambda

Changements pour Java

Gros problèmes

Inférence déjà existante pas assez puissante

Interface pas extensible

Et des petits +

Effectively finalPlus besoin de déclarer les variables locales utilisée dans les lambdas classes anonymes final

Eviter les garbages classesMettre les méthodes statiques public/private dans les interfaces

Page 30: Java 8 : Un ch'ti peu de lambda

Améliorer l'inférence

Java 5/7 infére

les variables de type des méthodesList<String> list =Array.asList("foo", "bar")

List<String> list = Collections.emptyList();

les instantiations de types paramétrésList<String> list = new ArrayList<>();

=> mais f(Collections.emptyList()) marche pas !

Page 31: Java 8 : Un ch'ti peu de lambda

Inference avec Java 8

Inference pour les types des lambdas

donc de gauche à droite comme les <>

Inference pour appel de méthode/instantiation diamond (JEP 101)

si dans un appel de méthodefoo(new ArrayList<>()); // ok

avec propagationString s = Collections.emptyList().get(0); // ok

Page 32: Java 8 : Un ch'ti peu de lambda

Les demi-dieux de l'inférence

Dan Smith Maurizio Cimadamore

Page 33: Java 8 : Un ch'ti peu de lambda

Extensibilité des interfaces

On veux obtenir un Stream à partir d'une Collection

collection.stream()

On ne peut pas ajouter une méthode dans une interface !

Solution académique: traits (!= Scala traits)

Page 34: Java 8 : Un ch'ti peu de lambda

Trait

Un type contenant des méthodes abstraites et des méthodes concrètes (mais pas de champs)

Ajouter une méthode si l'on fournit l'implantation ne casse pas la compatibilité

idée: et si on pouvait mettre du code dans une interface

Page 35: Java 8 : Un ch'ti peu de lambda

but, you broke Java !

Page 36: Java 8 : Un ch'ti peu de lambda
Page 37: Java 8 : Un ch'ti peu de lambda

Default method

Permet de fournir un code par défaut qui est utiliser si il n'en n'existe pas

interface Iterator<T> { public boolean hasNext(); public T next();

public default void remove() { throw new UnsupportedOperationException(); }

public default void forEach(Block<? super T> block) { while(hasNext()) { block.accept(it.next()); } }}

Page 38: Java 8 : Un ch'ti peu de lambda

Sémantique

La méthode par défaut n'est utilisée que “par défaut”

interface A { default void m() { ... }}

class B implements A{ void m() { ... } // pas besoin de A::m !}

Page 39: Java 8 : Un ch'ti peu de lambda

Héritage mutiple ??

interface A { default void m() { ... }}

interface B { default void m() { ... }}

class C implements A, B { // compile pas, faut choisir A::m ou B::m }

Page 40: Java 8 : Un ch'ti peu de lambda

Héritage mutiple

interface A { default void m() { ... }}

interface B { default void m() { ... }}

class C implements A, B { // on doit fournir un code pour m() public void m() { A.super.m(); // on appel m de A B.super.m(); // on appel m de B } }

Page 41: Java 8 : Un ch'ti peu de lambda

Partie IIIdans les entrailles de la VM

Page 42: Java 8 : Un ch'ti peu de lambda

Au menu ...

Comment les méthodes par défaut fonctionnent ?

Comment les lambdas sont compilés ?

Comment les lambdas sont optimisées par la VM ?

Page 43: Java 8 : Un ch'ti peu de lambda

Méthode par défaut

Le compilo ne peut rien faire !

Sinon on doit recompiler toutes les libraries

Doit être fait par la VM

mais● les règles de redéfinition (override) dépendent des generics ● Les générics sont un artifact à la compile pas connu à

l'exécution (erasure)

La VM doit savoir lire les signatures des generics

Page 44: Java 8 : Un ch'ti peu de lambda

Méthode par défaut et erasure

interface Foo<T> { default T getFoo() { return ...; }}interface Bar { String getFoo();}class A implements Bar, Foo<String> {}

Page 45: Java 8 : Un ch'ti peu de lambda

Méthode par défaut et erasure

interface Foo<T> { default ObjectT getFoo() { return ...; }}interface Bar { String getFoo();}class A implements Bar, Foo<String> {}

la VM doit générer deux méthodes Object getFoo() et String getFoo()

Page 46: Java 8 : Un ch'ti peu de lambda

Méthode par défaut et erasure

interface Foo<T> { default ObjectT getFoo() { return ...; }}interface Bar { String getFoo();}class A implements Bar, Foo<String> { Object getFoo() { return getFoo(); // appel String getFoo() } String getFoo() { return ...; // recopie le bytecode de Foo::getFoo }}

Page 47: Java 8 : Un ch'ti peu de lambda

Compiler une lambda naïvement

On créé une méthode synthetic pour le corps de la lambda

On crée une classe anonyme lors de la convertion vers la functional interface

iterator.forEach(dir -> System.out.println(dir));

devient

iterator.forEach(new Block<File>() { public void accept(File dir) { return lambda$1(dir); }});

static void lambda$1(File dir) { System.out.println(dir);}

Page 48: Java 8 : Un ch'ti peu de lambda

Lambda objet constant ?

A l'exécution, il y a deux sortes de lambdas

Les lambdas qui ne captures pas de variable ou les méthodes référence sur une classe

● path -> path.isDirectory● File::isDirectory

Celles qui capture des variables ou les méthode référence sur des instances

● path -> path.getName().startsWith(args[0)● System.out::println

Page 49: Java 8 : Un ch'ti peu de lambda

Compiler vers une classe anonyme ?

Et on se récupère tous les problèmes de perf des classes anonymes

De plus, si une lambda ne capture pas de variable, on pourrait au moins la crée que une seule fois

Mais si on utilise un champ static final, l'initialisation à lieu même si on ne l'utilise pas

Page 50: Java 8 : Un ch'ti peu de lambda

Invokedynamic to rule them all

On veut un mécanisme qui délai l'initialisation au premier accès

invokedynamic

On veut un mécanisme qui permet d'indiquer que le résultat d'un calcul est constant

invokedynamic

On veut un pointeur de fonction pour éviter la création des classe anonymes

java.lang.invoke.MethodHandle

Page 51: Java 8 : Un ch'ti peu de lambda

Compiler une lambda

iterator.forEach(dir -> System.out.println(dir));

devient

iterator.forEach(invokedynamic bootstrap [lambda$1]);

static void lambda$1(File dir) { System.out.println(dir);}CallSite bootstrap(Lookup lookup, String name, MethodType type, MethodHandle mh) { if (type.parameterCount() == 0) { return new ConstantCallSite(proxy(mh)); } return ...}

Page 52: Java 8 : Un ch'ti peu de lambda

Lambda Proxy

Instance d'une classe qui contient le pointeur de fonction (MethodHandle) vers la lambda à exécuter

Il n'y a besoin que d'une classe proxy par functional interface

Le proxy est généré dynamiquement par la VM,pas forcément besoin de bytecode associé

Page 53: Java 8 : Un ch'ti peu de lambda

Lambda Proxy en pseudo code

Le code Java correspondant est à peu près :

public class Proxy$Block implements Block { private final @Stable MethodHandle mh;

public void accept(Object o) { mh.invokeExact(o); }}

invokeExact() est un pointer check + un appel à un pointeur de fonction

Page 54: Java 8 : Un ch'ti peu de lambda

Optimizations lors de l'appel

Avoir 1 seul classe pour 1 interface permet à la VM de remplacer l'appel à l'interface par le code de la classe(Class Hierarchy Analysis)

interface Iterator<T> { ... public default void forEach(Block<? super T> block) { while(hasNext()) { block.accept(it.next()); } }}

ici, block est toujours une instance de Proxy$Block

Page 55: Java 8 : Un ch'ti peu de lambda

Optimizations lors de l'appel

Si un objet constant dans une boucle, il est sortie de la boucle

interface Iterator<T> { ... public default void forEach(Block<? super T> block) { while(hasNext()) { mh.invokeExact(it.next()); } }}

Dans le cas d'un method handle, il faut inliner le code référencé par le pointer de fonction (fairy tale mode)

Page 56: Java 8 : Un ch'ti peu de lambda

dans le monde merveilleuxdes licornes

On guarde la boucle avec un test sur le method handle(comme pour l'On Stack Replacement)

interface Iterator<T> { ... public default void forEach(Block<? super T> block) { if (block.mh == lambda$1) { while(hasNext()) { System.out.println(it.next()); } } else deopt(); }}

et comme block est pris en paramètre, l'idée est de spécialiser le code à l'endroit de l'appel ce qui permet de ne pas faire le test

Page 57: Java 8 : Un ch'ti peu de lambda

Le léger hic

Le code de la VM qui crée le lambda proxy et qui l'optimise est pas prêt

Le plan est prêt depuis longtemps, c'est la réalisation qui prend du temps

On doit quand même livrer un truc pour le JDK8

Solution temporaire: la méthode de bootstrap génère une classe anonyme dynamiquement en utilisant ASM

On implantera les lambda proxies dans une update du jdk8

Page 58: Java 8 : Un ch'ti peu de lambda

En résumé

La première version des lambdas sera pas la plus optimisée

L'API dans java.util doit être fini pour fin janvier

La spec de l'inférence pas fini même si ça se précise

Bref on est grâve à la bourre

Page 59: Java 8 : Un ch'ti peu de lambda

Si vous avez du temps libre

et même si vous n'en avez pas !

Downloader la dernière version du jdk8 avec les lambdas

http://jdk8.java.net/lambda/

Tester l'API, jouer avec, remonter tous les bugs

[email protected]