WTF ? WTF ? (What's the Fold) Olivier Croisier principes de programmation fonctionnelle, expliqués simplement
Jun 16, 2015
WTF ?WTF ?(What's the Fold)
Olivier Croisier
principes de programmation fonctionnelle, expliqués simplement
TheCodersBreakfast.net
github.com/OlivierCroisier
@OlivierCroisier
Olivier Croisier
JavaSpecialist™Consultant @Zenika
FormateurSpeakerBlogger
SPEAKER
Programmation fonctionnelle
Haskell, Scala... ?
Difficile ?
Vocabulaire Functor, Applicative, Monoid, Monad, Arrow... map(), filter()... fold()
FOLD ?
Définition en Haskell
… Pas de panique !
foldl :: (a b a) a [b] a→ → → → →foldl :: (a b a) a [b] a→ → → → →
FOLD ?
Exercice 1
Soit une liste d'entiers → Les additionner
List<Integer> nums = Arrays.asList(1,2,3,4,5);List<Integer> nums = Arrays.asList(1,2,3,4,5);
FOLD ?
Exercice 1
Soit une liste d'entiers → Les additionner
public Integer sum(List<Integer> nums) { Integer sum = 0; for (Integer num : nums) { sum = sum + num; } return sum;}
public Integer sum(List<Integer> nums) { Integer sum = 0; for (Integer num : nums) { sum = sum + num; } return sum;}
FOLD ?
List<Integer> nums = Arrays.asList(1,2,3,4,5);List<Integer> nums = Arrays.asList(1,2,3,4,5);
Exercice 2
Soit une liste d'entiers → Les multiplier
List<Integer> nums = Arrays.asList(1,2,3,4,5);List<Integer> nums = Arrays.asList(1,2,3,4,5);
FOLD ?
public Integer product(List<Integer> nums) { Integer product = 1; for (Integer num : nums) { product = product * num; } return product;}
public Integer product(List<Integer> nums) { Integer product = 1; for (Integer num : nums) { product = product * num; } return product;}
FOLD ?
Exercice 2
Soit une liste d'entiers → Les multiplier
List<Integer> nums = Arrays.asList(1,2,3,4,5);List<Integer> nums = Arrays.asList(1,2,3,4,5);
Pattern commun
Accumulateur ← valeur initiale Boucle sur la liste Opération (accumulateur, élément)
public Integer foo(List<Integer> nums) { Integer accu = <init>; for (Integer num : nums) { accu = accu <op> num; } return accu;}
public Integer foo(List<Integer> nums) { Integer accu = <init>; for (Integer num : nums) { accu = accu <op> num; } return accu;}
FOLD ?
public <A, E> A fold (BiFunction<A, E, A> op, A init, List<E> list) {
A accu = init; for (E num : list) { accu = op.apply(accu, num); } return accu;
}
public <A, E> A fold (BiFunction<A, E, A> op, A init, List<E> list) {
A accu = init; for (E num : list) { accu = op.apply(accu, num); } return accu;
}
Type de l'accumulateur
Type des éléments de la liste
FOLD ?
BiFunction<Integer,Integer,Integer> plus = new BiFunction<>() { public Integer apply(Integer accu, Integer elem) { return accu + elem; }};
BiFunction<Integer,Integer,Integer> mult = new BiFunction<>() { public Integer apply(Integer accu, Integer elem) { return accu * elem; }};
Integer sum = fold (plus, 0, nums);Integer product = fold (mult, 1, nums);
BiFunction<Integer,Integer,Integer> plus = new BiFunction<>() { public Integer apply(Integer accu, Integer elem) { return accu + elem; }};
BiFunction<Integer,Integer,Integer> mult = new BiFunction<>() { public Integer apply(Integer accu, Integer elem) { return accu * elem; }};
Integer sum = fold (plus, 0, nums);Integer product = fold (mult, 1, nums);
FOLD ?
BiFunction<Int,Int,Int> plus = (a,e) -> a+e;BiFunction<Int,Int,Int> mult = (a,e) -> a*e;
Integer sum = fold (plus, 0, nums);Integer product = fold (mult, 1, nums);
BiFunction<Int,Int,Int> plus = (a,e) -> a+e;BiFunction<Int,Int,Int> mult = (a,e) -> a*e;
Integer sum = fold (plus, 0, nums);Integer product = fold (mult, 1, nums);
Java 8 : Expressions Lambda
(args) -> expression
Integer sum = fold ((a,e)->a+e, 0, nums);Integer product = fold ((a,e)->a*e, 1, nums);Integer sum = fold ((a,e)->a+e, 0, nums);Integer product = fold ((a,e)->a*e, 1, nums);
FOLD ?
public class MathUtil { public static Integer mult(Integer x,Integer y) {
return x * y; }}
Integer prod = fold (MathUtil::mult, 1, nums);
public class MathUtil { public static Integer mult(Integer x,Integer y) {
return x * y; }}
Integer prod = fold (MathUtil::mult, 1, nums);
Java 8 : Références de méthodes
Class::staticFunction
Integer sum = fold (Integer::plus, 0, nums);Integer sum = fold (Integer::plus, 0, nums);
FOLD ?
Définition en Haskell
foldl :: (a b a) a [b] a→ → → → →foldl :: (a b a) a [b] a→ → → → →
fonction (a,b) a→
accumulateurde type a
liste<b>
résultat
FOLD ?
En programmation fonctionnelle, l'opération fold (ou reduce) est une famille de fonctions d'ordre supérieur qui traitent une structure de données dans un certain ordre pour produire un résultat.
– http://www.haskell.org/haskellwiki/Fold
“
Définition
PRINCIPES
Avantages
Mécanisme très générique Fonction d'ordre supérieur
Encapsulation de l'itération Expressivité ("quoi" vs "comment") Optimisation des opérations associatives
Famille de fonctions foldl, foldr, foldl1, foldr1 scanl, scanr, scanl1, scanr1
PRINCIPES
Fold left - foldl
0 + 1 [1,2,3,4,5]
1 + 2 [1,2,3,4,5]
3 + 3 [1,2,3,4,5]
6 + 4 [1,2,3,4,5]
10 + 5 [1,2,3,4,5]
15
0 + 1 [1,2,3,4,5]
1 + 2 [1,2,3,4,5]
3 + 3 [1,2,3,4,5]
6 + 4 [1,2,3,4,5]
10 + 5 [1,2,3,4,5]
15
accumulateur
PRINCIPES
Fold right - foldr
[1,2,3,4,5] 5 + 0
[1,2,3,4,5] 4 + 5
[1,2,3,4,5] 3 + 9
[1,2,3,4,5] 2 + 12
[1,2,3,4,5] 1 + 14
15
[1,2,3,4,5] 5 + 0
[1,2,3,4,5] 4 + 5
[1,2,3,4,5] 3 + 9
[1,2,3,4,5] 2 + 12
[1,2,3,4,5] 1 + 14
15
accumulateuritérationinversée !
PRINCIPES
Fold left vs Fold right
Opérations non commutatives (soustraction, division...)
Performances Plus efficace d'ajouter en tête des listes → foldr : élément à gauche, liste à droite
foldl (-) 0 [1..5]foldr (-) 0 [1..5]foldl (-) 0 [1..5]foldr (-) 0 [1..5]
quiz !
PRINCIPES
Autres folds
foldl1, foldr1 Même principe que foldl et foldr Accumulateur ← 1° élément de la liste
foldl (+) 0 [1..5]foldl1 (+) X [1..5]foldl (+) 0 [1..5]foldl1 (+) X [1..5]
valeur initiale implicite
PRINCIPES
Autres folds
scanl, scanr scanl1, scanr1 Même principe Renvoient toutes les valeurs intermédiaires
scanl (+) 0 [1..5][0,1,3,6,10,15]
scanr (+) 0 [1..5][15,14,12,9,5,0]
scanl (+) 0 [1..5][0,1,3,6,10,15]
scanr (+) 0 [1..5][15,14,12,9,5,0]
PRINCIPES
Folds complexes
Fold sert de base à beaucoup d'algorithmes impliquant le parcours d'une liste
Le résultat peut être une valeur unique ("reduce") ou une autre liste ! Dépend de l'accumulateur et de l'opération On peut effectuer une opération complexe par composition de fonctions simples
f(g(x)) (f g)(x)↔ ⋅f(g(x)) (f g)(x)↔ ⋅
USAGES
Fonction map
Applique une fonction f à chaque élément
Implémentation avec foldr Accumulateur ← liste cible vide Pour chaque élément e : - Calculer f(e) - Ajouter f(e) à la liste cible
[1,2,3,4,5] [→ f(1),f(2),f(3),f(4),f(5)][1,2,3,4,5] [→ f(1),f(2),f(3),f(4),f(5)]
fonctioncomposée
USAGES
Fonction map
result = foldr ((:).(*2)) [] [1..5]result = foldr ((:).(*2)) [] [1..5]
fonction f
composition de fonctions- application de f- puis ajout à la liste
"cons"
USAGES
Fonction map
List<Integer> accu = new ArrayList<>(); BiFunction<List<Integer>,Integer,List<Integer>> op = (l,e) -> { l.add(0, e * 2); return l; }; List<Int> result = foldr(op, accu, nums);
List<Integer> accu = new ArrayList<>(); BiFunction<List<Integer>,Integer,List<Integer>> op = (l,e) -> { l.add(0, e * 2); return l; }; List<Int> result = foldr(op, accu, nums);
USAGES
Fonction filter
Sélectionne uniquement les éléments qui répondent à un prédicat p
Implémentation avec foldr Accumulateur ← liste cible vide Pour chaque élément e : - Vérifier p(e) - Si p(e), ajouter e à la liste cible
[1,2,3,4,5] -(garder si – >3)--> [4,5][1,2,3,4,5] -(garder si – >3)--> [4,5]
USAGES
fonctioncomposée
Fonction filter
let op e list = if e > 3 then (e:list) else list
result = foldr op [] [1..5]
let op e list = if e > 3 then (e:list) else list
result = foldr op [] [1..5]
ajout de l'élément à l'accumulateur
USAGES
Fonction filter
List<Integer> accu = new LinkedList<>();
BiFunction<List<Integer>,Integer,List<Integer>> op = (l, e) -> { if (e > 3) l.add(0,e); return l; };
List<Int> result = foldr(op, accu, nums);
List<Integer> accu = new LinkedList<>();
BiFunction<List<Integer>,Integer,List<Integer>> op = (l, e) -> { if (e > 3) l.add(0,e); return l; };
List<Int> result = foldr(op, accu, nums);
USAGES
Fonction count
Compte le nombre d'éléments dans la liste
Implémentation avec foldl Accumulateur ← 0 Pour chaque élément e : - Incrémenter l'accumulateur
[1,2,3,4,5] 5→[1,2,3,4,5] 5→
USAGES
Fonction count
count = foldl (\a e a+1) 0 [1..5]→count = foldl (\a e a+1) 0 [1..5]→
Integer result = foldl((a,e)->a+1, 0, nums)Integer result = foldl((a,e)->a+1, 0, nums)
USAGES
Fonction max
Renvoie le plus grand élément de la liste
Implémentation avec foldl Accumulateur ← 0 Pour chaque élément e : - Si e > accumulateur, alors accumulateur = e
[1,2,3,4,5] 5→[1,2,3,4,5] 5→
USAGES
Fonction max
let op e m = if e > m then e else m
result = foldl op 0 [1..5]
let op e m = if e > m then e else m
result = foldl op 0 [1..5]
Integer max = foldl((e,m)-> e>m?e:m, 0, nums);Integer max = foldl((e,m)-> e>m?e:m, 0, nums);
USAGES
Java 8
Notion de Stream (java.util.stream.Stream) Pipeline de transformation Spécialisé par type (IntStream…) Implémente reduce (foldl restreint à 1 type)
T reduce( T identity, BinaryOperator<T> reducer );T reduce( T identity, BinaryOperator<T> reducer );
List<Integer> nums = Arrays.asList(1,2,3,4,5);
int sum = nums.stream() .reduce(0, Integer::sum);
List<Integer> nums = Arrays.asList(1,2,3,4,5);
int sum = nums.stream() .reduce(0, Integer::sum);
IMPLEMENTATIONS
Javascript
Sur les tableaux reduce (foldl) et reduceRight (foldr)
array.reduce (func, initval) array.reduceRight(func, initval)array.reduce (func, initval) array.reduceRight(func, initval)
var nums = new Array(1,2,3,4,5);
var sum = nums.reduce( function(a,e) {return a+e;}, 0);
var nums = new Array(1,2,3,4,5);
var sum = nums.reduce( function(a,e) {return a+e;}, 0);
IMPLEMENTATIONS
Scala
Sur les listes et tableaux foldLeft (/:) et foldRight (:\)
List.foldLeft (initval)(func) List.foldRight(initval)(func)List.foldLeft (initval)(func) List.foldRight(initval)(func)
val nums = Array(1, 2, 3, 4, 5)
val sum = nums.foldLeft(0)(_+_)val sum = (0 /: list)(_+_)
val nums = Array(1, 2, 3, 4, 5)
val sum = nums.foldLeft(0)(_+_)val sum = (0 /: list)(_+_)
IMPLEMENTATIONS
Groovy
Sur les listes list.inject (foldl)
nums = [1,2,3,4];
sum = nums.inject(0) { a,e -> a+e }
nums = [1,2,3,4];
sum = nums.inject(0) { a,e -> a+e }
IMPLEMENTATIONS
Alors, What's The Fold ?
Un principe simple, puissant et générique
Socle pour d'autre opérations map, filter, count...
Déjà présent dans vos langages reduce, inject, foldLeft... Apprenez à le reconnaître !
CONCLUSION
questions ?questions ?