Par
Vivian Pennel / @vp3n
Lead Developer chez IOcean
Les langages fonctionnels existent depuis toujours ou presque
Ils reviennent sur le devant de la scène, mais pas forcément dans le sens "pur" du terme
Pourquoi ce paradigme n'a pas été plus utilisé plus tôt ?
Partial application, Currying, Pattern Matching, Lazy evaluation, Continuations, Monads ...
Exemples en javascript
[1,2,3,4,5].map(function(a) { return a + 1} );
output : [2,3,4,5,6]
[1,2,3,4,5].reduce(function(memo, a) { return memo + a} );
output : 13
Existe aussi avec reduceRight()
[1,2,3,4,5].filter(function(a) { return a >= 4 } );
output : [4, 5]
Natif avec ECMAScript 5
Partout avec underscore.js
Ecrire des boucles manuellement est sujet à erreur
Réduit la quantité de code
Pas seulement pour les collections
Abusez-en !
Java < 1.8 : Guava (tranform(), filter())
PHP : array_map(), array_reduce(), array_filter()
Modification d'un état externe
I/O, UI etc..
Une application a besoin d'effets de bord mais...
rendent le code difficilement testable
rendent l'ordre d'appel des fonctions primordial
rendent difficile l'exécution parallèle du code (synchronisation, lock, écrasement...)
Que faire ?
La majorité des fonctions doit être pure
Les effets de bord doivent être limités à une(des) section(s) de l'application
function showNegativeNumber($numberList) {
foreach($numberList as $number) {
if($number < 0) echo $number;
}
}
Comment tester ce code ?
function filterNegative($numberList) {
return array_filter($numberList, function($number) {
return $number < 0;
});
}
function showNumbers($numberList) {
foreach($numberList as $number) {
echo $number;
}
}
filterNegative est parfaitement testable Simplement une bonne pratique
Respecte le principe SRP (Single Responsability Principle)
Utilisable partout
Exemples en Java
class Foo {
private List myList = new ArrayList();
public Foo() { myList.add(1);myList.add(3); }
public int sumWith(int a) {
int sum = 0; myList.add(a);
for(Integer i : myList) sum += i;
return sum;
}
public List getMyList() {
return myList;
}
}
Résultat si : foo.sumWith(1) ?
public class FooG {
public int sumWith(List myList, int a) {
int sum = 0;
for(Integer i : myList) sum += i;
return sum + a;
}
}
Pour toute valeur de "myList" et "a" équivalente, chaque appel retournera systématiquement la même somme
myList n'est jamais modifiée
public class FooG {
public int sumWith(List myList, int a) {
return myList.reduce( (memo, i) -> memo + i) + a;
}
}
Plus aucune variable
Limite les effets de bord
Facilite la comprehension du code
Facilite les tests
Une fonction pure est forcément transparente
Fonction dans une fonction
Scope de la fonction parente conservée après exécution
var myObj = function() {
var myPrivateVar = 10;
var myPrivateFunc = function() {
myPrivateVar++;
return myPrivateVar;
}
return {
myPublicFunc : function() {
return myPrivateFunc();
}
}
};
var o = myObj();
o.myPublicFunc()); //11
o.myPublicFunc()); //12
o.myPrivateFunc()); //o has not method myPrivateFunc()
Javascript, Java, Python, C# etc...
Hadoop (Java)
Traitement de quantité astronomique de données (logs ..)
Parrallélisation du traitement sur plusieurs coeurs et plusieurs serveurs
Node(Javascript), Playframework (Java, Scala), Servlet 3+ etc..
Abstraction au delà des threads/processus
Système distribué, fault-tolerant, daemon, async messaging
Exemples : Akka (Java, Scala), Theron(C++), Erlang (natif)
De plus en plus de concepts sont intégrés dans les langages impératifs
Il faut pouvoir en profiter
Des langages hybrides montent en puissance (Javascript, Scala, Ruby)
Le plus difficile c'est de changer la manière dont on construit le programme
et donc penser différement
On nous a appris à programmer de manière impérative
Le plus simple pour y parvenir est de s'imerger
Apprendre avec un langage à tendance fonctionnelle très importante
Le(s)quel(s) ?
for(
p <- persons
if !p.male
c <- p.children
) yield (p.name, c.name)
<=>
persons.filter(p => !p.male)
.flatMap(p => (
p.children.map( c => (p.name, c.name))
))
La version Java rentre pas dans une slide :)
bigList.par.map(line => processItem(line))
def findById(id: Int): Option[User] = users.get(id)
Récupérer une valeur sans se préocuper de si la valeur est nulle ou pas
findById(1).map(_.age) // Some(valeurAge)
Et si l'age peut être lui même "null" ?
findById(1).map(_.age) // Option[Option[Int]]
findById(1).flatMap(_.age) // Some(valeuAge)
//ou encore
for(user <- findById(1); age <- user.age) yield (age)
//filtrage
findById(1).filter(_.age > 30) // Some(valeurAge) si condition remplie
def doStep1():Future[Int] = ...
for {
step1 <- doStep1()
step2 <- doStep2()
step3 <- doStep3()
step4 <- doStep4(step1, step3) // seul exécution déterministe,
//nécessite des valeurs des futures précédentes
} yield combine(step1, step3)
def apply[B](f1: (A1, A2, A3) => B, f2: B => (A1, A2, A3))
(implicit fu: InvariantFunctor[M]): M[B] =
fu.inmap[A1 ~ A2 ~ A3, B](
canBuild(m1, m2), { case a1 ~ a2 ~ a3 => f1(a1, a2, a3) },
(b: B) => { val (a1, a2, a3) = f2(b); new ~(new ~(a1, a2), a3)}
)