Dans les traboulesdes lambdas
Rémi ForaxUPEMMai 2012
2
Moi
MCF à l'Université Paris Est Marne-la-vallée
Joue avec Java depuis trop longtemp pour l'avouer
Créateur de langages dynamiques ou pas
Expert pour les JSR 292 (invokedynamic) et JSR 335 (lambda)
Contributeur OpenJDK, ASM, Tatoo, PHP.reboot, JDart, etc...
3
Un exemple de lambda
enum Gastronomy { rosette, andouillette, coq_au_vin, tripe, cardoon}
private static List<Gastronomy> getGastronomyList() { List<Gastronomy> list = Arrays.asList(Gastronomy.values()); Collections.sort(list, new Comparator<Gastronomy>() { @Override public int compare(Gastronomy g1, Gastronomy g2) { return g1.name().compareTo(g2.name()); } }); return list;}
public static void main(String[] args) { System.out.println(getGastronomyList());}
4
Exemple un poil plus compliqué
private static ArrayList<Gastronomy> prefixList(String prefix, List<Gastronomy> list) { ArrayList<Gastronomy> list2 = new ArrayList<>(); for(Gastronomy gastronomy: list) { if (gastronomy.name().startsWith(prefix)) { list2.add(gastronomy); } } return list2;}
private static List<Gastronomy> getGastronomyList(String prefix) { List<Gastronomy> list = Arrays.asList(Gastronomy.values()); ArrayList<Gastronomy> list2 = prefixList(prefix, list); Collections.sort(list2, new Comparator<Gastronomy>() { @Override public int compare(Gastronomy g1, Gastronomy g2) { return g1.name().compareTo(g2.name()); } }); return list2;}
5
Parties codantes
private static ArrayList<Gastronomy> prefixList(String prefix, List<Gastronomy> list) { ArrayList<Gastronomy> list2 = new ArrayList<>(); for(Gastronomy gastronomy: list) { if (gastronomy.name().startsWith(prefix)) { list2.add(gastronomy); } } return list2;}
private static List<Gastronomy> getGastronomyList(String prefix) { List<Gastronomy> list = Arrays.asList(Gastronomy.values()); ArrayList<Gastronomy> list2 = prefixList(prefix, list); Collections.sort(list2, new Comparator<Gastronomy>() { @Override public int compare(Gastronomy g1, Gastronomy g2) { return g1.name().compareTo(g2.name()); } }); return list2;}
6
Closure ??
Expression que l'on veut voir comme une valeur
Les closures/lambda existent dans plein d'autres langages
Lisp/Clojure, Ruby, Groovy, Scala, C#, JavaScript/Python, et même C++/Objective C récemment
En Java, on a des classes annonymes
Youpi !Nan, je rigole :)
7
Problème des classes anonymes
Verbeux visuellement
rapport signal/bruit pas terrible
Sémantiquement bizarre
on veut envoyer une expression, créont une classe ...
Perf discutable (pour des closures)
création d'une instance à chaque appel
+ 1 classe sur le disque
+ 1 instance de java.lang.Class et ses métadatas en mémoire
8
Exemple de lambdas(sans lambdas)
private static List<Gastronomy> getGastronomyList(String prefix) { List<Gastronomy> list = Arrays.asList(Gastronomy.values()); ArrayList<Gastronomy> list2 = prefixList(prefix, list); Collections.sort(list2, new Comparator<Gastronomy>() { @Override public int compare(Gastronomy g1, Gastronomy g2) { return g1.name().compareTo(g2.name()); } }); return list2;}
9
Exemple de lambdas(avec lambdas)
private static List<Gastronomy> getGastronomyList(String prefix) { List<Gastronomy> list = Arrays.asList(Gastronomy.values()); ArrayList<Gastronomy> list2 = prefixList(prefix, list); Collections.sort(list2, (Gastronomy g1, Gastronomy g2) -> { return g1.name().compareTo(g2.name()); }); return list2;}
La syntaxe utilise -> (light arrow) et pas => (fat arrow) comme en Scala ou en C#
10
Inférence de type !
Le compilateur peut calculer le type des paramétres d'une lambda !
la méthode sort() prend une liste de Gastronomydonc elle attends un Comparator<Gastronomy>
private static List<Gastronomy> getGastronomyList(String prefix) { List<Gastronomy> list = Arrays.asList(Gastronomy.values()); ArrayList<Gastronomy> list2 = prefixList(prefix, list); Collections.sort(list2, (Gastronomy g1, Gastronomy g2) -> { return g1.name().compareTo(g2.name()); }); return list2;}
Le compilo n'utilise pas l'expression de la lambda pour faire l'inférence !
11
Exemple de lambdas(avec lambdas + inférence)
private static List<Gastronomy> getGastronomyList(String prefix) { List<Gastronomy> list = Arrays.asList(Gastronomy.values()); ArrayList<Gastronomy> list2 = prefixList(prefix, list); Collections.sort(list2, (g1, g2) -> g1.name().compareTo(g2.name()) ); return list2;}
12
Syntaxe: Lambda expression
sans paramètre
() -> System.out.println("Vive l'OL")
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
13
Syntaxe: Lambda instruction
sans paramètre
() -> { System.out.println("Vive l'OL");}
avec un paramètre (+ inférence)
employee -> { return employee.isManager();}
avec plusieurs paramètres (+ inférence)
(index1, index2) -> { list.set(index1, list.get(index2));}
14
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 interfaceRunnable r = () -> System.out.println("LOL?")
objet pas objet
15
Et les collections ?
Ici on filtre une liste, donc au lieu de
private static ArrayList<Gastronomy> prefixList( String prefix, List<Gastronomy> list) {
ArrayList<Gastronomy> list2 = new ArrayList<>(); for(Gastronomy gastronomy: list) { if (gastronomy.name().startsWith(prefix)) { list2.add(gastronomy); } } return list2;}
16
Et les collections ?
On aimerait bien écrire
private static List<Gastronomy> prefixList( String prefix, List<Gastronomy> list) { return list.filter(g -> g.name().startsWith(prefix)) .toList()}
Operation lazyOperation pas lazy
Heu, toList() sur une liste, vraiment ??
17
Programmation déclarative
list.filter(...) .map(...) .reduce(...)
on veut éviter les structures de données intermédiaire
lambdas
list filter map reduce
list filter map
reduce
Création (pipeline)
exécution
18
Programmation déclarative
Permet la programmation parallèle
On split les données en amont en on envoie le calcul sur différentes threads
● Disponible pour Java 8
Permet d'utiliser les GPUs
On émet le code PTX/HSAIL correspondant● Projet Sumatra (prototype avec Graal)
With great power there must also come — great responsibility!
L'utilisateur ne doit pas faire d'effet de bord dansles lambdas !!
19
Interface java.util.Stream
Sequentielle ou parallelelist.stream() ou list.parallelStream()
Deux types de methodeintermédiaire
filter, map, sorted, distinct, flatMap ...
terminalesforEach, reduce, collect, findFirst ...
L'implantation n'utilise pas de structures intermédaires mais un pipeline !
Un effet de bord, t'es mort !
20
Sans l'API des streams
private static ArrayList<Gastronomy> prefixList( String prefix, List<Gastronomy> list) {
ArrayList<Gastronomy> list2 = new ArrayList<>(); for(Gastronomy gastronomy: list) { if (gastronomy.name().startsWith(prefix)) { list2.add(gastronomy); } } return list2;}
21
Avec l'API des streams
private static ArrayList<Gastronomy> prefixList( String prefix, List<Gastronomy> list) {
return list.stream() .filter(g -> g.name().startsWith(prefix)) .collect(Collectors.toList());}
Bonus! pas besoin de déclarer la variable locale final !
22
Le coté sombre
>= JDK8< JDK 8
Où sont passées mes belles interfaces ?
23
Traits
Pour ajouter le support des lambdas au collections
Les méthodes stream() et parallelStream()
Il faut pouvoir ajouter des méthodes à une interface
Il faut mettre du code dans les interfaces
Don't panic
Cela s'appelle des traits(trivia: les traits de Scala ne sont pas des traits)
24
Default methods
Une méthode qui a du code dans une interface doit être taggée default
Une méthode par défaut est utilisée par une classe si il n'y a pas de méthode définie ou fournie par une sous classe
public interface Iterator<T> { public abstract boolean hasNext(); public abstract T next(); public default void remove() { throw new UnsupportedOperationException(); }}
25
Et le problème du diamand
Une interface accepte du code
mais pas de champs
Pas de problème de redéfinition de champ donc
Problème si on implante deux interfaces qui ont chacune une méthode ayant du code– Si une interface hérite de l'autre, on prend la méthode de la
sous-interface
– Sinon, le compilo plante
interface I { default void m() { } }interface J { default void m() { } }class A implements I, J { // compile pas}
26
Super sur des interfaces
La syntaxe, I.super.m() permet d'appeler la méthode m de l'interface I.
donc pour résoudre le conflit
interface I { default void m() { } }interface J { default void m() { } }class A implements I, J { public void m() { I.super.m(); }}
27
Retrofit
Avec Java 8, l'interface java.util.List possède enfin une méthode sort (par défaut)
qui appele Collections.sort :)
private static List<Gastronomy> getGastronomyList(String prefix) { List<Gastronomy> list = Arrays.asList(Gastronomy.values()); ArrayList<Gastronomy> list2 = prefixList(prefix, list); list2.sort((g1, g2) -> g1.name().compareTo(g2.name())); return list2;}
28
Avoir une méthode intermédiaire n'est plus nécessaire, on peut tout écrire dans une seule méthode
Et en utilisant Comparators.comparing()
private static List<Gastronomy> getGastronomyList(String prefix) { return Arrays.stream(Gastronomy.values()) .filter(g -> g.name().startsWith(prefix)) .sorted(Comparators.comparing(g -> g.name())) .collect(Collectors.toList());}
public static void main(String[] args) { System.out.println(getGastronomyList("c"));}
Exemple (suite)
29
Method Reference
Il existe une syntaxe spécifique pour les lambdas qui appel juste une méthode
g -> g.name() peut s'écrire Gastronomy::name
On ne spécifie pas le type des paramètres,le compilateur utilise la fonctional interface pour les trouver
Il est aussi possible de capturer une valeur
Callable<String> c = "foo"::toUpperCase()System.out.println(c.call()); // prints FOO
30
Avec des Method References
Et tout dans le main.
public static void main(String[] args) { Arrays.stream(Gastronomy.values()) .filter(g -> g.name().startsWith("c")) .sorted( Comparators.comparing(Gastronomy::name)) .forEach(System.out::println);}
31
Hunder the hoodHunder the hood
32
Compilation - Création
Une référence sur une méthode est crée en utilisant invokedynamic
BiFunction<String, Integer, Character> fun = String::charAt;
est transformé par le compilateur en
code:0: invokedynamic lambda() #0 ()Ljava/util/function/BiFunction;5: astore_1
33
Compilation - Création
constants:#0: #15 invokestatic java/lang/invoke/LambdaMetafactory.metaFactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
Method arguments: #16 invokeinterface java/util/function/BiFunction.apply :(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; #17 invokevirtual java/lang/String.charAt:(I)C #18 (Ljava/lang/String;Ljava/lang/Integer;)Ljava/lang/Character;
code:0: invokedynamic lambda() #0 ()Ljava/util/function/BiFunction;5: astore_1
Functional interface
Methodede l'interface
Methoderéférencée
Signature générique réifiée
34
Intérêt d'utiliser invokedynamic
La création de l'objet implantant la functional interface n'est pas écrite dans le code généré
Peux changer en fonction des versions du JDK, des optimisations implantées dans la VM
Si l'objet est constant, il peut être ré-utilisé
Pour le jdk8, utilise ASM pour générer une classe proxy dynamiquement
et sans vérification du bytecode
35
Compilation - Appel
L'appel se fait en utilisant la méthode de l'interface
BiFunction<String, Integer, Character> fun = ...char c = fun.apply("foo", 0);
et en bytecode
7: ldc #3 // String “foo” 9: iconst_0 10: invokestatic #4 // Integer.valueOf:(I)Ljava/lang/Integer;13: invokeinterface #5, 3 // BiFunction.apply (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;18: checkcast #6 // class Character21: invokevirtual #7 // Character.charValue:()C24: istore_2
36
Et les perfs ? / un nano-test
public class HoodPerf { private static int COUNTER;
private static void test() { BiFunction<String, Integer, Character> fun = String::charAt; char c = fun.apply("foobar", 5); COUNTER += c; // side effect }
public static void main(String[] args) { for(int i=0; i<10_000; i++) { test(); } System.out.println(COUNTER); System.out.println(end – start); }}
Ce code ne marchepas pour faire unbenchmark mais ...
37
pour voir de l'assembleur ...
{0x00007fd655851530} 'test' '()V' in 'HoodPerf'...d64d074300: mov %eax,-0x14000(%rsp)...d64d074307: push %rbp...fd64d074308: sub $0x20,%rsp...d64d07430c: mov $0xef010e68,%r10 ; {oop(a 'HoodPerf$$Lambda$1')}...d64d074316: mov 0x8(%r10),%r8d ; getClass...d64d07431a: mov $0xeeedaf80,%r11 ; {oop(a 'java/lang/Integer'[256] )}...d64d074324: mov 0x210(%r11),%r11d ;*aaload ; - java.lang.Integer::valueOf@21 (line 809)...d64d07432b: cmp $0xc6860240,%r8d ; {metadata('HoodPerf$$Lambda$1')}...d64d074332: jne ...d64d0743e9 ;*invokeinterface apply ; - HoodPerf::test@13 (line 10)...d64d074338: mov 0xc(%r11),%ebp ;*getfield value ; - java.lang.Integer::intValue@1 (line 871) ; implicit exception: dispatches to 0x00007fd64d074486...d64d07433c: test %ebp,%ebp...d64d07433e: jl ...d64d074401 ;*iflt ; - java.lang.String::charAt@1 (line 650)...d64d074344: cmp $0x3,%ebp...d64d074347: jge ...d64d074401 ;*if_icmplt ; - java.lang.String::charAt@10 (line 650)...d64d07434d: cmp $0x3,%ebp...d64d074350: jae ...d64d0743b7
ahah
charAt
38
Pour info, le code de String.charAt
public char charAt(int index) { if ((index < 0) || (index >= value.length)) { throw new StringIOOBException(index); } return value[index];}
...070d06a53c: test %edx,%edx
...070d06a53e: jl ...070d06a575 ;*iflt ; - java.lang.String::charAt@1 (line 650)...070d06a540: mov 0xc(%rsi),%ebp ;*getfield value ; - java.lang.String::charAt@6 (line 650)...070d06a543: mov 0xc(%rbp),%r10d ;*arraylength ; - java.lang.String::charAt@9 (line 650) ; implicit exception: dispatches to ...070d06a589...070d06a547: cmp %r10d,%edx...070d06a54a: jge ...070d06a575 ;*if_icmplt ; - java.lang.String::charAt@10 (line 650)...070d06a54c: cmp %r10d,%edx...070d06a54f: jae ...070d06a562...070d06a551: movzwl 0x10(%rbp,%rdx,2),%eax
39
et encore de l'assembleur ...
...d64d074352: mov $0xef015e10,%r10 ; {oop([C)}
...d64d07435c: movzwl 0x10(%r10,%rbp,2),%ebp ;*caload ; - java.lang.String::charAt@27 (line 653)...d64d074362: cmp $0x7f,%ebp...d64d074365: jg ...d64d074411 ;*if_icmpgt ; - java.lang.Character::valueOf@3 (line 4570)...d64d07436b: cmp $0x80,%ebp...d64d074371: jae 0x00007fd64d0743c9...d64d074373: mov $0xeef0e828,%r10 ; {oop(a 'java/lang/Character'[128] )}...d64d07437d: mov 0x10(%r10,%rbp,4),%r10d ;*aaload ; - java.lang.Character::valueOf@10 (line 4571)...d64d074382: test %r10d,%r10d...d64d074385: je ...d64d0743d9 ;*invokevirtual charValue...d64d074387: mov $0xeef05ab8,%r11 ; {oop(a 'java/lang/Class' = 'HoodPerf')}...d64d074391: mov 0x58(%r11),%r11d ;*invokestatic valueOf...d64d074395: movzwl 0xc(%r10),%r10d...d64d07439a: add %r10d,%r11d...d64d07439d: mov $0xeef05ab8,%r10 ; {oop(a 'java/lang/Class' = 'HoodPerf')}...d64d0743a7: mov %r11d,0x58(%r10) ;*putstatic COUNTER...d64d0743ab: add $0x20,%rsp...d64d0743af: pop %rbp...d64d0743b0: test %eax,0x9006c4a(%rip) # ...d65607b000 ; {poll_return}...d64d0743b6: retq Ça sert à quoi ??
charAt
40
... en résumé
Une méthode référence (ou une lambda) qui ne fait pas de capture est une constante
L'appel a une lambda peut être dévirtualisé et inliné comme n'importe quel appel
Le code montré est celui générer par le jdk8b88-lambda avec elision du boxing/unboxing desactivé :(
41
What's next ?
Updates du JDK8
lambda constante sans check de classe
lambda non-constante mieux dévirtualisée
Java 9
Value ObjectInteger, Float, etc ne devrait pas avoir d'identité
=> pas sûr que cela marche
Utilisation du/des GPUs dans les Stream parallele
42
Questions ?
JDK8 Feature Freeze jeudi prochain !
les lambdas sont déjà dans le JDK !
https://jdk8.java.net/download.html
Remonter les bugs sur
la mailing list: lambda-devhttp://mail.openjdk.java.net/mailman/listinfo/lambda-dev
ou bugs.sun.com