comprendre la programmation fonctionnelle, blend web mix le 02/11/2016
Post on 14-Apr-2017
185 Views
Preview:
TRANSCRIPT
Comprendre la programmation fonctionnelle
@loicknuchel
Loïc KnuchelGeek passionné
Développeur Scala
Organisateur
Débuter avec la programmation fonctionelle ?
High-order function
Pure function
Immutable
Functor
Currying
Monad
Applicative
RécursifMonoid
Au fait, c’est quoi la programmation fonctionnelle ?
“La programmation fonctionnelle est un paradigme de programmation qui considère le calcul en tant qu'évaluation de fonctions mathématiques.”
Wikipedia
“La programmation fonctionnelle est un style de programmation qui met l’accent sur les fonctions qui ne dépendent pas de l’état du programme.”
Functionnal programming in scala
“La programmation fonctionnelle permet de coder de manière plus productive, plus modulaire et avec moins de bugs.”
Loïc Knuchel ;)
Exemple ?
for loops are everywhere
function toUpperCase(list){ const ret = []; for(let i=0; i<list.length; i++){ ret[i] = list[i].toUpperCase(); } return ret;}
console.log(toUpperCase(['Finn', 'Rey', 'Poe']));// ['FINN', 'REY', 'POE']
for loops are everywhere
function toUpperCase(list){ const ret = []; for(let i=0; i<list.length; i++){ ret[i] = list[i].toUpperCase(); } return ret;}
console.log(toUpperCase(['Finn', 'Rey', 'Poe']));// ['FINN', 'REY', 'POE']
Interdiction de modifier les paramètres !!!
for loops are everywhere
function toUpperCase(list){ const ret = []; for(let i=0; i<list.length; i++){ ret[i] = list[i].toUpperCase(); } return ret;}
console.log(toUpperCase(['Finn', 'Rey', 'Poe']));// ['FINN', 'REY', 'POE']
Boilerplate !!!
for loops are everywhere
Array.prototype.map = function(transform){ const array = this; const result = []; for(let i=0; i<array.length; i++){ result[i] = transform(array[i]); } return result;};
for loops are everywhere
Array.prototype.map = function(transform){ const array = this; const result = []; for(let i=0; i<array.length; i++){ result[i] = transform(array[i]); } return result;};
Fonction d’ordre supérieur
for loops are everywhere
function toUpperCase(list){ const ret = []; for(let i=0; i<list.length; i++){ ret[i] = list[i].toUpperCase(); } return ret;}
function toUpperCase(list){ return list.map(item => item.toUpperCase());}
Séparation technique vs métier
Array.prototype.map = function(transform){ var array = this; var result = []; for(var i=0; i<array.length; i++){ result[i] = transform(array[i]); } return result;};
list.map(item => item.toUpperCase());
Générique / Réutilisable / StableAbstraction de haut niveau
Concis / Expressif / FlexibleFocalisé sur le domaine
Cas pratique
Température moyenne par localisation
const data = [ { coords: [42.097002, -79.235326], temperatures: [-34, 67, 101, 87] }, { coords: [38.888025, -121.016225], temperatures: [-3, 4, 9, 12] }, { coords: [40.462512, -99.249261], temperatures: [75, 75, 75, 75, 75] }, ...];
// expected format :[ // [coords, average temperature] [[42.097002, -79.235326], 55.25], [[38.888025, -121.016225], 5.5], [[40.462512, -99.249261], 75], ...]
Code impératif
function chartFormat(data){ var results = [], totalTemp = 0, averageTemp = 0; for(var i=0; i < data.length; i++) { totalTemp = 0; for(var j=0; j < data[i].temperatures.length; j++) { totalTemp += data[i].temperatures[j]; } averageTemp = totalTemp / data[i].temperatures.length; results.push([data[i].coords, averageTemp]); } return results;}
● Difficile à comprendre
● Pas réutilisable / générique
● Bugs probables
● Difficile à tester
Fonctionnel
● Immutabilité
Meilleure compréhension du codeFixe les problèmes :● Asynchrone● Concurrent● Scalabilité horizontale
Fonctionnel
● Immutabilité
● Stateless Raisonnement local
Couplage réduit
Testabilité
Fonctionnel
● Immutabilité
● Stateless
● Pas d’effet de bord
Effet de bord :● faire un appel (bdd, http, fichier…)● récupérer la date actuelle● accéder à une variable “globale”● modifier un paramètre● lancer une exception● afficher un log● ...
Fonctionnel
● Immutabilité
● Stateless
● Pas d’effet de bord
Raisonnement local
Couplage réduit
Composition facilitée
Testabilité
Fonctionnel
● Immutabilité
● Stateless
● Pas d’effet de bord
Functional core / Imperative shell
Fonctionnel
● Immutabilité
● Stateless
● Pas d’effet de bord
● Décomposer en fonction réutilisables
Moyenne des températures par point
Fonctionnel : moyenne des températures
function sum(numArr, currentTotal){ currentTotal = currentTotal || 0; if(numArr.length === 0){ return currentTotal; } else { return sum(numArr.slice(1), currentTotal + numArr[0]); }}
function avg(numArr){ return sum(numArr) / numArr.length;}
var averageTemp = avg(temperatures);
Fonctionnel : extraire les températures
var allTemperatures = data.map(function(item){ return item.temperatures;});
const data = [ { coords: [42.097002, -79.235326], temperatures: [-34, 67, 101, 87] }, { coords: [38.888025, -121.016225], temperatures: [-3, 4, 9, 12] }, { coords: [40.462512, -99.249261], temperatures: [75, 75, 75, 75, 75] }, ...];
Curryfication
function add1(b){ return 1+b;}
console.log(add1(2)); // 3
function addCurry(a){ return function(b){ return a+b; }}
var add1 = addCurry(1);var add2 = addCurry(2);
console.log(add1(2)); // 3console.log(add2(2)); // 4
Fonctionnel : extraire les températuresvar allTemperatures = data.map(function(item){ return item.temperatures;});
function getAttr(attrName){
return function(item){
return item[attrName];
}
}
var allTemperatures = data.map(getAttr('temperatures'));
function mapAttr(arr, attrName){
return arr.map(getAttr(attrName));
}
Array.prototype.mapAttr = function(attrName){ return mapAttr(this, attrName);};var allTemperatures = data.mapAttr('temperatures');
Fonctionnel : combiner nos données
var coordsList = data.mapAttr('coords'); // [[42.097, -79.235], ...]var avgTemps = data.mapAttr('temperatures').map(avg); // [55.25, 5.5, 75, ...]
function zip(arr1, arr2, resultArr){ resultArr = resultArr || []; if(arr1.length === 0 || arr2.length === 0){ return resultArr; } else { return zip(arr1.slice(1), arr2.slice(1), resultArr.concat([arr1[0], arr2[0]])); }}// zip([1, 2, 3], [‘a’, ‘b’, ‘c’]) => [[1, ‘a’], [2, ‘b’], [3, ‘c’]]
Array.prototype.zip = function(other){ return zip(this, other, []);};
var chartData = coordsList.zip(avgTemps);// [ [[42.097002, -79.235326], 55.25] , [[38.888025, -121.016225], 5.5], ... ]
Fonctionnel : tout combiner
function chartFormat(data){ return data.mapAttr('coords').zip(data.mapAttr('temperatures').map(avg));}
VS
function chartFormat(data){ var results = [], totalTemp = 0, averageTemp = 0; for(var i=0; i < data.length; i++) { totalTemp = 0; for(var j=0; j < data[i].temperatures.length; j++) { totalTemp += data[i].temperatures[j]; } averageTemp = totalTemp / data[i].temperatures.length; results.push([data[i].coords, averageTemp]); } return results;}
def chartFormat(data: List[((Double, Double), List[Double])]) = data.map(_._1).zip(data.map(_._2).map(t => t.sum / t.length))
Bilan
● Plus facile à :○ écrire○ lire○ maintenir
● Beaucoup moins de bugs
Scala collection API (petite partie)
def map[B](f: (A) => B): List[B]def filter(p: (A) => Boolean): List[A]def partition(p: (A) => Boolean): (List[A], List[A])def zip[B](that: List[B]): List[(A, B)]def sliding(size: Int): Iterator[List[A]]
def find(p: (A) => Boolean): Option[A]def exists(p: (A) => Boolean): Boolean
def flatten[B]: List[B]def flatMap[B](f: (A) => List[B]): List[B]
def groupBy[K](f: (A) => K): Map[K, List[A]]def grouped(size: Int): Iterator[List[A]]
def fold[A1 >: A](z: A1)(op: (A1, A1) => A1): A1def reduce[A1 >: A](op: (A1, A1) => A1): A1def forall(p: (A) => Boolean): Boolean
def take(n: Int): List[A]def drop(n: Int): List[A]def distinct: List[A]
Bugs are everywhere...
function toUpperCase(list){ const ret = []; for(let i=0; i<list.length; i++){ ret[i] = list[i].toUpperCase(); } return ret;}
Safe ?
Bugs are everywhere...
function toUpperCase(list){ const ret = []; for(let i=0; i<list.length; i++){ ret[i] = list[i].toUpperCase(); } return ret;}
Unsafe !
Cannot read property 'xxx'
of undefined !!!
Bugs are everywhere...
function toUpperCase(list){ const ret = []; for(let i=0; i<list.length; i++){ ret[i] = list[i].toUpperCase(); } return ret;}
function toUpperCase(list){ var ret = []; if(Array.isArray(list)) { for(var i=0; i<list.length; i++){ if(typeof list[i] === 'string'){ ret[i] = list[i].toUpperCase(); } else { throw "not a string"; } } } else { throw "not an array"; } return ret;}
Bugs are everywhere...
function toUpperCase(list){ const ret = []; for(let i=0; i<list.length; i++){ ret[i] = list[i].toUpperCase(); } return ret;}
function toUpperCase(list){ var ret = []; if(Array.isArray(list)) { for(var i=0; i<list.length; i++){ if(typeof list[i] === 'string'){ ret[i] = list[i].toUpperCase(); } else { ret[i] = list[i]; } } } return ret;}
Bugs are everywhere...
function toUpperCase(list){ const ret = []; for(let i=0; i<list.length; i++){ ret[i] = list[i].toUpperCase(); } return ret;}
function toUpperCase(list){ var ret = []; if(Array.isArray(list)) { for(var i=0; i<list.length; i++){ if(typeof list[i] === 'string'){ ret[i] = list[i].toUpperCase(); } else { ret[i] = list[i]; } } } return ret;}
Unreadable !
Bugs are everywhere...
function toUpperCase(list){ const ret = []; for(let i=0; i<list.length; i++){ ret[i] = list[i].toUpperCase(); } return ret;}
function toUpperCase(list){ var ret = []; if(Array.isArray(list)) { for(var i=0; i<list.length; i++){ ret[i] = list[i].toUpperCase(); } } return ret;}
function toUpperCase(list){ var ret = []; if(Array.isArray(list)) { for(var i=0; i<list.length; i++){ if(typeof list[i] === 'string'){ ret[i] = list[i].toUpperCase(); } else { ret[i] = list[i]; } } } return ret;}
Bugs are everywhere...
function toUpperCase(list){ const ret = []; for(let i=0; i<list.length; i++){ ret[i] = list[i].toUpperCase(); } return ret;}
function toUpperCase(list){ var ret = []; if(Array.isArray(list)) { for(var i=0; i<list.length; i++){ ret[i] = list[i].toUpperCase(); } } return ret;}
function toUpperCase(list){ var ret = []; if(Array.isArray(list)) { for(var i=0; i<list.length; i++){ if(typeof list[i] === 'string'){ ret[i] = list[i].toUpperCase(); } else { ret[i] = list[i]; } } } return ret;}
not so “smart”Cannot read property 'xxx'
of undefined !!!
Bugs are everywhere...
function toUpperCase(list){ const ret = []; for(let i=0; i<list.length; i++){ ret[i] = list[i].toUpperCase(); } return ret;}
function toUpperCase(list){ var ret = []; if(Array.isArray(list)) { for(var i=0; i<list.length; i++){ ret[i] = list[i].toUpperCase(); } } return ret;}
function toUpperCase(list){ var ret = []; if(Array.isArray(list)) { for(var i=0; i<list.length; i++){ if(typeof list[i] === 'string'){ ret[i] = list[i].toUpperCase(); } else { ret[i] = list[i]; } } } return ret;}
Unsafe !
not so “smart”
Unreadable !
Option
Option
val myMap = Map("key" -> "value")
val v1: Option[String] = myMap.get("key") // Some("value")
val v2: Option[String] = myMap.get("miss") // None
val v3: Option[String] = v1.map(_.toUpperCase) // Some("VALUE")
val v4: Option[String] = v2.map(_.toUpperCase) // None
val v5: String = v3.getOrElse("default") // "VALUE"
val v6: String = v4.getOrElse("default") // "default"
Option
def toUpperCase(list: List[String]) = list.map(_.toUpperCase)
def toUpperCase(list: List[Option[String]]) = list.map(_.map(_.toUpperCase))
def toUpperCase(list: Option[List[Option[String]]]) = list.map(_.map(_.map(_.toUpperCase)))
List.map() vs Option.map() ?
List.map() vs Option.map() ?
Fonctors !!!
def toWords(sentences: List[String]): List[List[[String]] = sentences.map(_.split(" ").toList)
def toWords(sentences: List[String]): List[String] = sentences.flatMap(_.split(" ").toList)
def toWords(sentences: List[String]): List[List[[String]] = sentences.map(_.split(" ").toList)
def toWords(sentences: List[String]): List[String] = sentences.flatMap(_.split(" ").toList)
Applicative !
def toWords(sentences: List[String]): List[List[[String]] = sentences.map(_.split(" ").toList)
def toWords(sentences: List[String]): List[String] = sentences.flatMap(_.split(" ").toList)
Applicative !
Fonctor + Applicative = Monad
def toWords(sentences: List[String]): List[List[[String]] = sentences.map(_.split(" ").toList)
def toWords(sentences: List[String]): List[String] = sentences.flatMap(_.split(" ").toList)
Future[A]Option[A]
List[A]Try[A]
Page[A]
Monads !!!
Level up your abstractions !
Take away
● Paramètre de fonction plutôt que donnée globale (même de classe)
● Créer des objets plutôt que de les modifier (immutable)
● Option plutôt que ‘null’
● Option / Try / Either / Validation plutôt qu’une exception
● Collection API / récursivité plutôt que boucles for/while
● Eviter les ‘if’ autant que possible
● Séparation technique / métier
● Functionnal core / Imperative shell
Références
Does the Language You Use Make a Difference ?
When DDD meets FP, good things happen
Philosohie fonctionnelle
Ur Domain Haz Monoids (vidéo)
DDD: et si on reprenait l'histoire par le bon bout ?
DDD, en vrai pour le développeur
Functional programming Illustrated by Scala
Scala School!
loicknuchel@gmail.com @loicknuchel http://loic.knuchel.org/
top related