🏋️♂️ Refactoring sans les for
🏋️♂️ Refactoring sans les
Back to Basics
Les frameworks et bibliothèques naissent et meurent, les bases restent.
Plan
Introduction
🔬 Anatomie d'une boucle
🔃 Récursion
🌊
Stream
🏆 Qui est le meilleur ?
Conclusion
🔬 Anatomie d'une boucle
🔬 Anatomie d'une boucle
Transformation
// Java 5 for Each
public Collection<Result> transform(Collection<Element> input) {
Collection<Result> results = new ArrayList<Result>();
for (Element element : input) {
Result res = transform(element);
results.add(res);
}
return results;
}
Filtre
public Collection<Element> filter(Collection<Element> input) {
Collection<Element> results = new ArrayList<>();
for (Element element : input) {
if(isSomething(element)) {
results.add(element);
}
}
return results;
}
Accumulation
public Accumulator compute(Collection<Element> input) {
Accumulator accumulator = initialValue;
for (Element element : input) {
// accumulate :: (Accumulator, Element) -> Accumulator
accumulator = accumulate(accumulator, element);
}
return accumulator;
}
Imbrication
for (Element element : input) {
for (Child child: element.getChildren()) {
// transform, filter, accumulate, ...
}
}
Et le reste
toto: for (Element element : input) {
tata: for (Child child : element.getChildren()) {
if(cond1) {
continue toto;
} else if (cond2) {
break tata;
}
// ...
}
}
🔃 Récursion
🔃 Récursion
Parcours - Java
public List<Result> transformR(List<Element> input) {
if (input.isEmpty()) { // end of recursion
return Collections.emptyList();
}
// Deconstruct
Element head = input.get(0);
List<Element> tail = input.subList(1, input.size() - 1);
// Handle head
Result transformed = transform(head);
// Recursion
List<Result> results = new ArrayList<>();
results.add(transformed);
results.addAll(transformR(tail));
return results;
}
Parcours - Kotlin & Scala
-
fun transformR(input: List<Element>): List<Result> = if (input.isEmpty()) listOf() else listOf(transform(input.first())) + transformR(input.drop(1))
-
def transformR(input: List[Element]): List[Result] = input match { case Nil => Nil case head :: tail => transform(head) :: transformR(tail) }
Filtre & Sortie rapide - Java
public Element find(List<Element> input) {
if (input.isEmpty()) {
return null;
}
Element head = input.get(0);
if (isSomething(head)) {
return head;
}
List<Element> tail = input.subList(1, input.size());
return find(tail);
}
Récursion non terminale
- `fact(x)`
-
- `x xx fact(x-1)`
-
- `x`
- `x xx (x-1) xx fact(x-2)`
-
- `x - 1`
- `x`
- `x xx (x-1) xx (x-2) xx ...`
-
- ...
- `x - 1`
- `x`
- `x xx (x-1) xx (x-2) xx ... xx 2 xx 1`
-
- `1`
- `2`
- ...
- `x - 1`
- `x`
Récursion terminale
- `fact(x) = fact(x, 1)`
- `fact(x-1, x xx 1)`
- `fact(x-2, x xx (x-1))`
- `fact(... , x xx (x-1) xx (x-2) xx ...)`
- `fact(1, x xx (x-1) xx (x-2) xx ... xx 2)`
-
⚠️ Nécessite une optimisation par le compilateur
- !!Con 2019- Tail Call Optimization: The Musical!! by Anjana Vakil & Natalia Margolis
Principe récursion terminale
-
tailRecFunc(scope, state) = if (isFinish(scope)) computeResult(state) else (head, subScope) := scope newState := reduce(state, head) tailRecFunc(subScope, newState)
-
public Accumulator compute(Collection<Element> input) { Accumulator accumulator = initialValue; for (Element element : input) { // accumulate :: (Accumulator, Element) -> Accumulator accumulator = accumulate(accumulator, element); } return accumulator; }
Récursion terminale - Java
Game Over
Insert Kotlin or ScalaTo continue
Récursion terminale - Kotlin & Scala
-
fun factorial(n: Int): Int { tailrec fun aux(n: Int, acc: Int): Int = if (n < 2) acc else aux(n - 1, n * acc) return aux(n, 1) }
-
def factorial(n: Int): Int = { @tailrec def aux(n: Int, acc: Int): Int = if (n < 2) acc else aux(n - 1, n * acc) aux(n, 1) }
🌊 Stream
🌊 Stream
filter, map, & flatMap
-
List<Integer> ints = new ArrayList<>(); for (int i = 0; i < 9; i++) { for (int j = 0; j < i; j++) { if (j % 2 == 1) { ints.add(j * j); } } }
-
List<Integer> ints = IntStream.range(0, 9) .flatMap(i -> IntStream.range(0, i)) .filter(j -> j % 2 == 1) .map(j -> j * j) .boxed() .collect(Collectors.toList());
Imbrication - flatMap 2/2
-
1 2 3
-
1.1 1.2 1.32 3
-
1.1 1.2 1.3 2 3
-
1.1 1.2 1.32.1 2.2 2.3 2.43
-
1.1 1.2 1.3 2.1 2.2 2.3 2.4 3
-
1.1 1.2 1.3 2.1 2.2 2.3 2.4
-
1.1 1.2 1.3 2.1 2.2 2.3 2.4
Stream is Lazy
String[] data = "lorem ... amet".split(" ");
Stream<String> stream = Arrays.stream(data)
.map(s -> {
System.out.print(s + ",");
return s.toUpperCase();
}).filter(s -> {
System.out.print(s + ",");
return s.startsWith("A");
}).peek(s -> System.out.println(s));
System.out.println("Stream created");
-
lorem,...,amet,LOREM,...,AMET,AMET Stream created
-
Stream created
Sloth
Opérations paresseuses & terminales
- Opérations paresseuses
map
filter
flatMap
peek
distinct
limit
etskip
- ...
- Opérations terminales
reduce
etcollect
findAny
etfindFirst
forEach
count
- ...
Accumulation - Reduce
// input: List<Element>
// accumulate : (Accumulator, Element) -> Accumulator
// Accumulator#merge : (Accumulator) -> Accumulator
Accumulator result =
input.stream()
.reduce(
new Accumulator(),
(acc, elt) -> accumulate(acc, elt),
(acc1, acc2) -> acc1.merge(acc2)
);
Si Element == Accumulator
, le accumulate
peut servir de merge
Les count
, min
, max
, sum
, ... sont des réductions particulières
Accumulation - collect & Collectors
// T: Type du Stream
// A: Type de l'accumulateur
// R: Type du résultat
interface Collector<T,A,R> {
Supplier<A> supplier();
BiConsumer<A,T> accumulator();
BinaryOperator<A> combiner();
Function<A,R> finisher();
// ...
}
toSet
, toList
, toMap
, groupBy
, joining
,
summarizingInt
, summarizingDouble
, summarizingLong
,
...
Et si j'ai besoin de l'index ?
-
😢 pas faisable facilement et proprement en Java
-
✌️ mais il y a Kotlin et Scala...
Nouveautés Java 9+
- Java 9
Stream#takeWhile
etStream#dropWhile
Stream#iterate
avec un prédicatStream#ofNullable
Optional#stream
Collectors#flatMapping
etCollectors#filtering
String#chars
etString#codePoints
- Java 10
Collectors#toUnmodifiableXXX
pour lesList
,Set
etMap
- Java 11
String#lines
- Java 12
Collectors#teeing
Bilan Stream
🤗
-
On peut utiliser intelligemment les aspects paresseux
-
Sans bonne raison, ne faites pas de
Stream
parallèleLa lisibilité est importante
-
À proscrire Les effets de bord ! (on tolère les logs dans le
peek
)Les opérations non associatives dans des
Stream
parallèlesLes streams sur des
Integer
,Double
,Long
Bilan Stream - Java
😡
-
T reduce(T identity, BinaryOperator<T> accumulator)
etOptional<T> reduce(BinaryOperator<T> accumulator)
-
IntStream
,DoubleStream
,LongStream
, avecmapToObj
,mapToXXX
, ... -
Le boilerplate, par exemple
.collect(Collectors.toList())
,Collectors.groupBy
, ... -
Pas de tuples en Java
-
Des 🐛 dans le lazy du
flatMap
, voir Java Stream API was Broken Before JDK10
Bilan Stream - Kotlin
😍
-
API lazy avec les
Sequence
ou non directement sur les collections -
API collection immutable ou mutable
-
🎭 utilise juste les classes de Java
Bilan Stream - Scala
😻
-
API lazy avec les
Stream
/LazyList
ou non directement sur les collections -
API collection immutable ou mutable
-
Pas de réutilisation de Java
-
API de
Stream
etLazyList
avec la possibilité de construction récursive -
De gros changements dans la 2.13
-
Le
for
de Scala est du sucre syntaxique qui produit desmap
,filter
,flatMap
Du coup on peut l'utiliser sur d'autre objects qui ont
map
,filter
,flatMap
🏆 Qui est le meilleur ?
🏆 Qui est le meilleur ?
Relation d'ordre
Si on veut déterminer le meilleur, ils nous faut une relation d'ordre, laquelle ?
-
🏎 le plus rapide ?
-
💾 le moins couteux en mémoire ?
-
🧱 le plus maintenable ?
-
🈂 le plus lisible ?
-
...
Java & performances
-
📏 faire des micro-benchmark en Java, c'est pas évident
-
♨️ la JVM à besoin de chauffer (JIT)
- JMH
- Microbenchmarking in Java with JMH (5 articles)
Rust
MonteCarlo π
MonteCarlo - Point
public class Point {
private final double x;
private final double y;
public Point(double x, double y) { this.x = x; this.y = y; }
public boolean inCircle() {
return (x * x) + (y * y) <= 1;
}
private static final Random rnd = new Random();
public static Point newPoint() {
return new Point(rnd.nextDouble(), rnd.nextDouble());
}
public static double compute(int count, int inCircle) {
return ((double) inCircle / count) * 4;
}
}
MonteCarlo - Java for
public static double monteCarloFor(int count) {
int inCircle = 0;
for (int i = 0; i < count; i++) {
Point p = newPoint();
if (p.inCircle()) {
inCircle++;
}
}
return compute(count, inCircle);
}
MonteCarlo - Java stream
public static double monteCarloStream(int count) {
int inCircle = (int) Stream.generate(Point::newPoint)
.limit(count)
.filter(Point::inCircle)
.count();
return compute(count, inCircle);
}
MonteCarlo - Java stream parallèle
public static double monteCarloStreamParallel(int count) {
int inCircle = (int) Stream.generate(Point::newPoint)
.unordered()
.parallel()
.limit(count)
.filter(Point::inCircle)
.count();
return compute(count, inCircle);
}
MonteCarlo - Kotlin for
fun monteCarloFor(count: Int): Double {
var inCircle = 0
for (i in 0 until count) {
val p = newPoint()
if (p.inCircle()) {
inCircle++
}
}
return compute(count, inCircle)
}
MonteCarlo - Kotlin tailrec
fun monteCarloRecursion(count: Int): Double {
tailrec fun aux(count: Int, inCircle: Int): Int =
if (count == 0) inCircle
else {
val p = newPoint()
aux(count - 1, if (p.inCircle()) inCircle + 1 else inCircle)
}
val inCircle = aux(count, 0)
return compute(count, inCircle)
}
MonteCarlo - Kotlin collection
fun monteCarloCollection(count: Int): Double {
val inCircle = (1..count)
.map { newPoint() }
.count { it.inCircle() }
return compute(count, inCircle)
}
MonteCarlo - Kotlin séquence
fun monteCarloSequence(count: Int): Double {
val inCircle = generateSequence { newPoint() }
.take(count)
.count { it.inCircle() }
return compute(count, inCircle)
}
MonteCarlo - Kotlin séquence parallèle *
fun monteCarloSequenceParallel(count: Int): Double {
val inCircle = sequence {
yieldAll(generateSequence { newPoint() })
}
.take(count)
.count { it.inCircle() }
return compute(count, inCircle)
}
MonteCarlo - Scala for
def monteCarloFor(count: Int): Double = {
var inCircle = 0
for (_ <- 1 to count) {
val p = newPoint()
if (p.inCircle()) {
inCircle += 1
}
}
compute(count, inCircle)
}
MonteCarlo - Scala tailrec
def monteCarloRecursion(count: Int): Double = {
@tailrec
def aux(count: Int, inCircle: Int): Int =
if (count == 0) inCircle
else {
val p = newPoint()
aux(count - 1, inCircle + (if (p.inCircle()) 1 else 0))
}
val inCircle = aux(count, 0)
compute(count, inCircle)
}
MonteCarlo - Scala collection
def monteCarloCollection(count: Int): Double = {
val inCircle = (1 to count)
.map(_ => newPoint())
.count(_.inCircle())
compute(count, inCircle)
}
MonteCarlo - Scala stream
def monteCarloStream(count: Int): Double = {
val inCircle = Stream.fill(count) {
newPoint()
}
.count(_.inCircle())
compute(count, inCircle)
}
MonteCarlo - Scala stream parallèle
def monteCarloStreamParallel(count: Int): Double = {
val inCircle = Stream.fill(count) {
newPoint()
}
.par
.count(_.inCircle())
compute(count, inCircle)
}
Disclaimer
Avec Java 11, Kotlin v1.3.31, Scala v2.13.0, sur un iMac13.2, avec un Intel core i7 3.4GHz - 4 cores, et le JRE HotSpot 11.0.3 AdoptOpenJDK
🎳 venez lire, tester, critiquer, proposer des PR sur le dépôt GithubREMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on why the numbers are the way they are. Use profilers (see
-prof
,-lprof
), design factorial experiments, perform baseline and negative tests that provide experimental control, make sure the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts.
Do not assume the numbers tell you what you want them to tell.
MonteCarlo - performance 1000 points
MonteCarlo - performance 10M points
MonteCarlo - performance parallèle
😱 le code parallèle
MonteCarlo - performance parallèle 2
Ce qui change
-
🎲 depuis Java 8
SplittableRandom
-
public class Point { private final double x; private final double y; public Point(double x, double y) { this.x = x; this.y = y; } public boolean inCircle() { return (x * x) + (y * y) <= 1; } private static final Random rnd = new Random(); private static final SplittableRandom rnd = new SplittableRandom(); public static Point newPoint() { return new Point(rnd.nextDouble(), rnd.nextDouble()); } public static double compute(int count, int inCircle) { return ((double) inCircle / count) * 4; } }
Separation of Concerns
-
List<String> errors = new ArrayList<>(); int errorCount = 0; File file = new File(fileName); String line = file.readLine(); while(errorCount < 40 && line != null) { if(line.startWith("ERROR")) { errors.add(line); errorCount++; } line = file.readLine(); }
-
List<String> errors = Files.lines(Paths.get(fileName)) .filter(line -> line.startsWith("ERROR")) .limit(40) .collect(toList());
Prédisposition aux 🐛
// Good old for style
public Collection transform(Collection input) {
Collection results = new ArrayList();
for (int i = 0; i < input.size(); i++) {
Element element = (Element) input.get(i);
Result res = transform(element);
results.add(res);
}
return results;
}
Prédisposition aux 🐛 dangereux
// Java 8 forEach & lambda expression
public Collection<Result> transform(Collection<Element> input) {
Collection<Result> results = new ArrayList<>();
input.forEach(element -> {
Result res = transform(element);
results.add(res); // 😨
});
return results;
}
Conclusion
Conclusion
Bilan
goto
firstQuote
Les frameworks et bibliothèques naissent et meurent, les bases et les styles de programmation restent.
Si vous avez du mal à choisirIl suffit de choisir le langage, les frameworks, et les bibliothèques qui correspondent aux styles.
Choisissiez un style qui correspond à vos contraintes et vos goûts.
FP
- Lambda et fonctions d'ordre supérieur
- Pas d'effet de bord
- immutabilité
- `=>` Ceci est une présentation sur la programmation fonctionnelle
FP 🍌
-
Functional Programming with Bananas, Lenses, Envelopes and Barbed Wire - Erik Meijer and J. Hughes and M.M. Fokkinga and Ross Paterson" - 1991
We develop a calculus for lazy functional programming based on recursion operators associated with data type definitions. For these operators we derive various algebraic laws that are useful in deriving and manipulating programs. We shall show that all example functions in Bird and Wadler's "Introduction to Functional Programming" can be expressed using these operators.
-
@rvirding @C0deAttack @viktorklang Recursion is the GOTO of functional programming (from 1991 http://t.co/RMhUTcDsFo).
— Erik Meijer (@headinthebox) September 29, 2013
Crafters
- ❓ Quand vous codez, posez-vous des questions !
- 🔪 Aiguisez votre esprit critique !
- 🗣 Partagez vos questionnements, vos solutions, vos idées farfelues !
Speaker
Merci
-
Questions ?
- (les retours sont bienvenus)