Advertisement
Advertisement

More Related Content

Advertisement

Recently uploaded(20)

Comprendre la programmation fonctionnelle, Blend Web Mix le 02/11/2016

  1. Comprendre la programmation fonctionnelle @loicknuchel
  2. Loïc Knuchel Geek passionné Développeur Scala Organisateur
  3. Débuter avec la programmation fonctionelle ?
  4. High-order function Pure function Immutable Functor Currying Monad Applicative Récursif Monoid
  5. 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 ;)
  6. Exemple ?
  7. 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']
  8. 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 !!!
  9. 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 !!!
  10. 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; };
  11. 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
  12. 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()); }
  13. 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 / Stable Abstraction de haut niveau Concis / Expressif / Flexible Focalisé sur le domaine
  14. Cas pratique
  15. 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] }, ... ];
  16. // expected format : [ // [coords, average temperature] [[42.097002, -79.235326], 55.25], [[38.888025, -121.016225], 5.5], [[40.462512, -99.249261], 75], ... ]
  17. 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
  18. Fonctionnel ● Immutabilité Meilleure compréhension du code Fixe les problèmes : ● Asynchrone ● Concurrent ● Scalabilité horizontale
  19. Fonctionnel ● Immutabilité ● Stateless Raisonnement local Couplage réduit Testabilité
  20. 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 ● ...
  21. Fonctionnel ● Immutabilité ● Stateless ● Pas d’effet de bord Raisonnement local Couplage réduit Composition facilitée Testabilité
  22. Fonctionnel ● Immutabilité ● Stateless ● Pas d’effet de bord Functional core / Imperative shell
  23. Fonctionnel ● Immutabilité ● Stateless ● Pas d’effet de bord ● Décomposer en fonction réutilisables
  24. Moyenne des températures par point
  25. 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);
  26. 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] }, ... ];
  27. 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)); // 3 console.log(add2(2)); // 4
  28. Fonctionnel : extraire les températures var 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');
  29. 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], ... ]
  30. 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))
  31. Bilan ● Plus facile à : ○ écrire ○ lire ○ maintenir ● Beaucoup moins de bugs
  32. 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): A1 def reduce[A1 >: A](op: (A1, A1) => A1): A1 def forall(p: (A) => Boolean): Boolean def take(n: Int): List[A] def drop(n: Int): List[A] def distinct: List[A]
  33. Bugs are everywhere... function toUpperCase(list){ const ret = []; for(let i=0; i<list.length; i++){ ret[i] = list[i].toUpperCase(); } return ret; } Safe ?
  34. 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 !!!
  35. 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; }
  36. 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; }
  37. 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 !
  38. 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; }
  39. 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 !!!
  40. 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 !
  41. Option
  42. 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"
  43. 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)))
  44. List.map() vs Option.map() ?
  45. List.map() vs Option.map() ? Fonctors !!!
  46. def toWords(sentences: List[String]): List[List[[String]] = sentences.map(_.split(" ").toList) def toWords(sentences: List[String]): List[String] = sentences.flatMap(_.split(" ").toList)
  47. def toWords(sentences: List[String]): List[List[[String]] = sentences.map(_.split(" ").toList) def toWords(sentences: List[String]): List[String] = sentences.flatMap(_.split(" ").toList) Applicative !
  48. 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
  49. 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 !!!
  50. Level up your abstractions !
  51. 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
  52. 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!
  53. loicknuchel@gmail.com @loicknuchel http://loic.knuchel.org/
Advertisement