Coding Dojo :
Programmation
Fonctionnelle
FP : pourquoi ?
Let’s say for a moment that you are a lumberjack. You have the best axe in the forest, which makes you the most productive lumberjack in the camp.
Then one day someone shows up and extols the virtues of a new tree-cutting paradigm, the chainsaw. The sales guy is persuasive, so you buy a chainsaw, but you don’t know how it works.
Demonstrating your expertise with the previous tree-cutting paradigm, you swing it vigorously at a tree—without cranking it. You quickly conclude that this newfangled chainsaw is a fad, and you return to your axe.
Then, someone appears and shows you how to crank the chainsaw...
“Functional Thinking: Functional Programming using Java, Clojure and Scala”,
published by O‘Reilly.
FP : à quoi ca sert ?
- Dans la vie de tous les jours, la FP sert à manipuler des collections de manière plus courte et plus expressive que des boucles for ou while
-
Il suffit principalement de 3 fonctions :
- ForEach
- Map
- Filter
- Ce coding dojo va vous expliquer ces fonctions et comment les combiner pour avoir un code plus court et plus expressif
Les lambda fonctions
-
Dans Programmation Fonctionnelle, il y a fonctionnelle :
- On va combiner des fonctions au lieu de combiner des instructions
-
Il faut donc utiliser un langage qui considère les fonctions comme des objets combinables : C#, Java 8+, Python, C++ 11, ...
-
=> On va utiliser javascript car il suffit d’un navigateur pour avoir un environnement de développement opérationnel !
Apprenons le javascript
-
Un tableau vide en js :
-
var people = []
-
var people = []
-
Un objet en js :
- var ben = {id:1, name: "Ben"}
- ben.id et ben.name pour accéder aux propriétés
Apprenons le javascript
-
Un tableau d’objet en js
-
var people= [{id:1,name:"Ben"}, {id:2,name:"Jafar"}, {id:3,name:"Matt"}]
-
var people= [{id:1,name:"Ben"}, {id:2,name:"Jafar"}, {id:3,name:"Matt"}]
-
Boucle en js :
-
for (var i = 0; i < people.length; i++) {
var e = people[i];
/*I can now use e.id and e.name*/
}
-
for (var i = 0; i < people.length; i++) {
Apprenons le javascript
-
Déclarer et appeler une fonction :
-
function test(arg1, arg2) {
/*I can now use arg1 and arg2*/
return arg1 + " " + arg2
} - test("Hello", "World") => "Hello World"
-
function test(arg1, arg2) {
-
Voici une fonction qu’on va utiliser ce soir
- function display(e) { console.log(e) };
ForEach
-
on ne comprend pas facilement ce qu’on fait dans une boucle...
-
var peopleNames= ["Ben", "Jafar","Matt"]
for (var i = 0; i < peopleNames.length; j++) {
display(peopleNames[i])
}
-
var peopleNames= ["Ben", "Jafar","Matt"]
ForEach
-
=> une boucle infinie qui affiche le premier nom...
-
forEach = appliquer une fonction sur chaque élément d’une collection
- peopleNames.forEach(display)
ForEach : exercice
-
afficher tous les noms à l’aide de display avec forEach
- var people= [{id:1,name:"Ben"}, {id:2,name:"Jafar"}, {id:3,name:"Matt"}]
ForEach : exercice
-
Solution possible :
-
function printName(p) { display(p.name); } people.forEach(printName)
-
function printName(p) { display(p.name); } people.forEach(printName)
- Petit soucis : la fonction printName fait 2 choses : elle extrait le nom et l’affiche
Map
-
Quand on travaille sur un élément d’une collection, on a souvent besoin de le transformer pour qu’il soit utilisable dans une autre fonction
- => Ca s’appelle effectuer une projection
-
function extractName(p) {
return p.name
} - => je projette le nom de la personne
Map
-
map = appliquer une projection à tous les éléments d’une collection
- [{id:1,name:"Ben"}].map(extractName) => ["Ben"]
Map : exercice
-
afficher tous les noms à l’aide de display avec forEach et map
- var people= [{id:1,name:"Ben"}, {id:2,name:"Jafar"}, {id:3,name:"Matt"}]
Map : exercice
-
Solution possible :
-
function extractName(p) { return p.name} people.map(extractName).forEach(display)
-
function extractName(p) { return p.name} people.map(extractName).forEach(display)
- Petit soucis : on ne peut pas choisir de ne pas traiter un élément
Filter
-
Quand on travaille sur une collection, on a souvent besoin de travailler sur certains éléments uniquement
- => Ca s’appelle effectuer un filtre
-
Pour effectuer un filtre, on a besoin de respecter un prédicat
- function isMale(p) {
return p.gender == 'MALE'
} - => prédicat pour filtrer les hommes
- function isMale(p) {
Filter
- Filter = appliquer un prédicat à tous les éléments d’une collection
- [{id:1,gender:"MALE"}, {id:2,gender:"FEMALE"}]
.filter(isMale)
=> [{id:1, gender:"MALE"}]
- [{id:1,gender:"MALE"}, {id:2,gender:"FEMALE"}]
Filter : exercice
- afficher tous les noms qui commence par
un B à l’aide de display avec forEach et
map et filter-
var people= [{id:1,name:"Ben"},
{id:2,name:"Jafar"}, {id:3,name:"Matt"}]
-
var people= [{id:1,name:"Ben"},
Filter : exercice
- Solution possible :
-
function extractName(p) { return p.name}
function hasNameStartingWithB(p) {
return p.name[0] == 'B'
}
people.filter(hasNameStartingWithB)
.map(extractName)
.forEach(display)
-
function extractName(p) { return p.name}
- Petit soucis : faire la même chose avec les noms qui commencent par d’autres lettres oblige à refaire 26 fonctions
Créer des fonctions
-
Quand on travaille avec des fonctions, on a parfois besoin de fabriquer des fonctions (en général avec moins d’arguments...)
-
function adder(x) {
return function(y) { return x+ y};
} -
=> crée une fonction pour ajouter x
-
-
On parle de fonction d'ordre supérieur currifiée
Créer des fonctions
-
Particulièrement utile avec map et filter bien sûr
-
[1, 2, 3, 4].map(adder(2)) => [3, 4, 5, 6]
-
[1, 2, 3, 4].filter(isModulo(2)) => [2, 4]
-
people
.filter(hasNameStartingWith('C'))
.map(extractName)
.forEach(display)
Créer des fonctions :exercice
-
var NASDAQ = [
{name: "ANGI", price: 31.22, stamp: new Date(2011,11,15) },
{name: "MSFT", price: 32.32, stamp: new Date(2011,11,15) },
{name: "GOOG", price: 150.43, stamp: new Date(2011,11,15)},
{name: "ANGI", price: 28.44, stamp: new Date(2011,11,16)},
{name: "GOOG", price: 199.33, stamp: new Date(2011,11,16)}];
-
Afficher le prix des actions de X (ex: "GOOG" ) en date du Z (ex: 2011-11-16)
-
comparer 2 dates en JS :
d1.getTime() == d2.getTime()
-
Créer des fonctions :exercice
-
Solution possible
function isActionOf(tm) { return function(a) { return a.name == tm;} }
function isActionAt(y, m, d) {
return function(a) { return new Date(y, m, d).getTime() == a.stamp.getTime(); }
}
function extractPrice(a) { return a.price; } - pricesNASDAQ
.filter(isActionOf("GOOG"))
.filter(isActionAt(2011,11,16))
.map(extractPrice)
.forEach(display) - Petit soucis : c'est lisible mais...
DSL
- On aimerait pouvoir lire le code comme
on lit de l’anglais -
Cela est possible si on arrive à créer un
Domain Specific Language- new Prices(nasdaq)
.of("GOOG")
.at(2011,11,16)
.forEach(display)
- new Prices(nasdaq)
Les classes en JS
-
En JS, on peut créer des classes !
-
function MyC(arg) {
this.prop = arg;
}
MyC.prototype.myfn = function (arg) {
return this.prop + arg;
}
-
function MyC(arg) {
DSL : exercice
- Ecrire une classe Prices qui permet de faire
- new Prices(nasdaq)
.of("GOOG")
.at(2011,11,16)
.forEach(display)
DSL : exercice
- Solution possible
- function Prices(list) { this.list = list; }
Prices.prototype.of = function (tm) {
return new Prices(this.list.filter(isActionOf(tm)))
}
Prices.prototype.at = function (year, month, day) {
return new Prices(this.list.filter(isActionAt(year, month, day)))
}
Prices.prototype.forEach = function(fn) { this.list.forEach(fn); } -
Bon ce n'est toujours pas de l'anglais ...
- new Prices(nasdaq).of("GOOG").at(2011,11,16).forEach(display)
DSL : en vrai !
-
Dans un "vrai" langage fonctionnel, les parenthèses, les virgules, les points et les new sont optionnels (le compilateur les déduit)
-
new Prices(NASDAQ).of("GOOG").at(2011,11,16)
Est équivalent à
Prices NASDAQ of "GOOG" at 2011 11 16
-
-
=> c’est quasiment de l’anglais !!!
Autres utilisations ?
- La FP n’est pas seulement idéale pour manipuler les collections, elle est également idéale pour les traitements asynchrones.
-
function every(t, fn) { setTimeout(fn, t); }
function combine(f1, f2) { return f2(f1()); }
//exo : combiner n fonctions -
every(SECOND(1), combine(sensor.read, display))
every(SECOND(1), combine(sensor.read, extractTemp, display))
Autres utilisations ?
- La FP est également utile pour l’écriture de GUI
- Lancer une recherche quand l’utilisateur a tapé au moins 3 caractères sans lancer 2 fois la même recherche
- Rx.fromEvent(input, ‘keyup’) // créer une collection d’évènements
.map(e => e.target.value) //retenir la valeur du champ texte
.filter(q => q.length > 2) //seulement si au moins 3 caractères .distinctUntilChanged() //seulement si différent du précédent .flatMapLatest(searchAndDisplay) //et envoyer le dernier à la fonction
Coding Dojo Programmation Fonctionnelle
By Christophe Blin
Coding Dojo Programmation Fonctionnelle
Une introduction à la programmation fonctionnelle au travers de petits exercices de code accessibles à tous
- 2,312