La programmation fonctionnelle est un style de programmation qui commence à se populariser. Cependant, elle garde un côté compliqué et inaccessible ce qui n'est absolument pas le cas.
Le but de cette présentation est de montrer pourquoi la programmation fonctionnelle est intéressante et surtout comment s'y mettre par petites étapes :)
Les exemples sont montrés en JavaScript / Java / Scala pour rester le plus accessible et voir les différences entre ces langages.
16. 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
17. 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
18. 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 et
plus modulaire, avec moins de bugs.” Moi
19.
20. Transformer un tableau
function toUpperCase(list){
var ret = [];
for(var i=0; i<list.length; i++){
ret[i] = list[i].toUpperCase();
}
return ret;
}
var names = ['Finn', 'Rey', 'Poe'];
console.log(toUpperCase(names));
// ['FINN', 'REY', 'POE']
public List<String> toUpperCase(List<String> list) {
List<String> ret = new ArrayList<>();
for(String item : list){
ret.add(item.toUpperCase());
}
return ret;
}
List<String> names = Arrays.asList("Finn", "Rey",
"Poe");
System.out.println(Arrays.toString(toUpperCase(names).
toArray()));
// [FINN, REY, POE]
25. Séparation technique / métier
Array.prototype.map = function(callback){
var array = this;
var result = [];
for(var i=0; i<array.length; i++){
result[i] = callback(array[i]);
}
return result;
};
list.map(function(item){
return item.toUpperCase();
});
Générique
Haut niveau d’abstraction
Un maximum de libraires externe
26. Séparation technique / métier
Array.prototype.map = function(callback){
var array = this;
var result = [];
for(var i=0; i<array.length; i++){
result[i] = callback(array[i]);
}
return result;
};
list.map(function(item){
return item.toUpperCase();
});
Générique
Haut niveau d’abstraction
Un maximum de libraires externe
Concis / Expressif
Focalisé sur le domaine
Un minimum de libraires externe
29. function getPictures(items, id){
var pictures = [];
for (var i=0; i<items.length; i++) {
var item = items[i];
if (item.id === id) {
for (var j=0; j<item.actions.length; j++) {
var action = item.actions[j];
if (action.name === 'sendPicture') {
for (var k=0; k<action.pictures.length; k++) {
var picture = action.pictures[k];
if (!picture.deleted) {
pictures.push(picture);
}
}
}
}
}
}
return pictures;
}
Manipuler des données
public List<Picture> getPictures(List<Item> items, String id) {
List<Picture> pictures = new ArrayList<>();
for (Item item : items) {
if (item.getId() == id) {
for (Action action : item.getActions()) {
if (action.getName() == "sendPicture") {
for (Picture picture : action.getPictures()) {
if (!picture.getDeleted()) {
pictures.add(picture);
}
}
}
}
}
}
return pictures;
}
30. function getPictures(items, id){
var pictures = [];
for (var i=0; i<items.length; i++) {
var item = items[i];
if (item.id === id) {
for (var j=0; j<item.actions.length; j++) {
var action = item.actions[j];
if (action.name === 'sendPicture') {
for (var k=0; k<action.pictures.length; k++) {
var picture = action.pictures[k];
if (!picture.deleted) {
pictures.push(picture);
}
}
}
}
}
}
return pictures;
}
Manipuler des données
public List<Picture> getPictures(List<Item> items, String id) {
List<Picture> pictures = new ArrayList<>();
for (Item item : items) {
if (item.getId() == id) {
for (Action action : item.getActions()) {
if (action.getName() == "sendPicture") {
for (Picture picture : action.getPictures()) {
if (!picture.getDeleted()) {
pictures.add(picture);
}
}
}
}
}
}
return pictures;
}
Duplication !
Duplication !
31. function getPictures(items, id){
var pictures = [];
for (var i=0; i<items.length; i++) {
var item = items[i];
if (item.id === id) {
for (var j=0; j<item.actions.length; j++) {
var action = item.actions[j];
if (action.name === 'sendPicture') {
for (var k=0; k<action.pictures.length; k++) {
var picture = action.pictures[k];
if (!picture.deleted) {
pictures.push(picture);
}
}
}
}
}
}
return pictures;
}
Manipuler des données
public List<Picture> getPictures(List<Item> items, String id) {
List<Picture> pictures = new ArrayList<>();
for (Item item : items) {
if (item.getId() == id) {
for (Action action : item.getActions()) {
if (action.getName() == "sendPicture") {
for (Picture picture : action.getPictures()) {
if (!picture.getDeleted()) {
pictures.add(picture);
}
}
}
}
}
}
return pictures;
}
Find item by id
Filter actions by name
Filter pictures not deleted
32. function getPictures(items, id){
var pictures = [];
for (var i=0; i<items.length; i++) {
var item = items[i];
if (item.id === id) {
for (var j=0; j<item.actions.length; j++) {
var action = item.actions[j];
if (action.name === 'sendPicture') {
for (var k=0; k<action.pictures.length; k++) {
var picture = action.pictures[k];
if (!picture.deleted) {
pictures.push(picture);
}
}
}
}
}
}
return pictures;
}
Manipuler des données
public List<Picture> getPictures(List<Item> items, String id) {
List<Picture> pictures = new ArrayList<>();
for (Item item : items) {
if (item.getId() == id) {
for (Action action : item.getActions()) {
if (action.getName() == "sendPicture") {
for (Picture picture : action.getPictures()) {
if (!picture.getDeleted()) {
pictures.add(picture);
}
}
}
}
}
}
return pictures;
}
Find item by id
Filter actions by name
Filter pictures not deleted
Level up your abstraction !
33. Manipuler des données
// Finds the 1st elt of the sequence satisfying a predicate, if any
def find[A](p: (A) => Boolean): Option[A]
// Selects all elts of this collection which satisfy a predicate
def filter[A](p: (A) => Boolean): List[A]
// Builds a new list by applying a function to all elements of the list
def map[A, B](f: (A) => B): List[B]
// Applies a binary operator to all elts of this list
def reduce[A, B](f: (B, A) => B, b: B): B
58. Typage fort
● Filet de sécurité pour garantir la cohérence du programme
● Documentation pour le développeur
59. Typage fort
● Filet de sécurité pour garantir la cohérence du programme
● Documentation pour le développeur
● Implémentent certains concepts
60. Typage fort
● Filet de sécurité pour garantir la cohérence du programme
● Documentation pour le développeur
● Implémentent certains concepts
● null => Option
61. Typage fort
● Filet de sécurité pour garantir la cohérence du programme
● Documentation pour le développeur
● Implémentent certains concepts
● null => Option
● exception => Either / Try
62. Typage fort
● Filet de sécurité pour garantir la cohérence du programme
● Documentation pour le développeur
● Implémentent certains concepts
● null => Option
● exception => Either / Try
● async => Future
● ...
63. Typage fort
● Filet de sécurité pour garantir la cohérence du programme
● Documentation pour le développeur
● Implémentent certains concepts
● Type Driven Development
64. Type all the things !!!
case class Contact(
firstName: String,
middleInitial: String,
lastName: String,
emailAddress: String,
isEmailVerified: Boolean
)
65. Type all the things !!!
case class Contact(
firstName: String,
middleInitial: String,
lastName: String,
emailAddress: String,
isEmailVerified: Boolean
)
Optionnel ?
66. Type all the things !!!
case class Contact(
firstName: String,
middleInitial: String,
lastName: String,
emailAddress: String,
isEmailVerified: Boolean
)
Optionnel ?
Contrainte ?
67. Type all the things !!!
case class Contact(
firstName: String,
middleInitial: String,
lastName: String,
emailAddress: String,
isEmailVerified: Boolean
)
Optionnel ?
Contrainte ?
Lien ?
68. Type all the things !!!
case class Contact(
firstName: String,
middleInitial: String,
lastName: String,
emailAddress: String,
isEmailVerified: Boolean
)
Optionnel ?
Contrainte ?
Lien ?
Logique métier ?
69. Type all the things !!!
case class Contact(
firstName: String,
middleInitial: String,
lastName: String,
emailAddress: String,
isEmailVerified: Boolean
)
case class Contact(
name: PersonalName,
email: EmailAddress)
Optionnel ?
Contrainte ?
Lien ?
Logique métier ?
70. Type all the things !!!
case class Contact(
firstName: String,
middleInitial: String,
lastName: String,
emailAddress: String,
isEmailVerified: Boolean
)
case class Contact(
name: PersonalName,
email: EmailAddress)
case class PersonalName(
firstName: String_50,
middleInitial: Option[String_1],
lastName: String_50)
sealed trait EmailAddress
case class VerifiedEmail(value: Email) extends EmailAddress
case class UnverifiedEmail(value: Email) extends EmailAddress
Optionnel ?
Contrainte ?
Lien ?
Logique métier ?
71. Type all the things !!!
case class Contact(
firstName: String,
middleInitial: String,
lastName: String,
emailAddress: String,
isEmailVerified: Boolean
)
case class Contact(
name: PersonalName,
email: EmailAddress)
case class PersonalName(
firstName: String_50,
middleInitial: Option[String_1],
lastName: String_50)
sealed trait EmailAddress
case class VerifiedEmail(value: Email) extends EmailAddress
case class UnverifiedEmail(value: Email) extends EmailAddress
case class String_1(value: String) {
require(value.length <= 1, s"String_1 should be <= 1 (actual: $value)")
override def toString: String = value
}
case class String_50(value: String) {
require(value.length <= 50, s"String_50 should be <= 50 (actual: $value)")
override def toString: String = value
}
case class Email(value: String) {
require(value.contains("@"), s"Email should contain '@' (actual: $value)")
override def toString: String = value
}
Optionnel ?
Contrainte ?
Lien ?
Logique métier ?
80. Immutabilité
● Scalabilité / Multithreading
● Meilleur nommage
● Séparation données / calculs
Types et Fonctions plutôt que Classes :
● Entity
● Value Object
● Service
81. Immutabilité
● Scalabilité / Multithreading
● Meilleur nommage
● Séparation données / calculs
Types et Fonctions plutôt que Classes :
● Entity
● Value Object
● Service
case class Person(
firstName: String,
lastName: String) {
val fullName = Person.fullName(this)
}
object Person {
def fullName(p: Person): String =
p.firstName+" "+p.lastName
}
82. No side effect
Effet de bord: lancer une exception, faire un appel (bdd, http, fichier…), récupérer la date actuelle,
modifier un paramètre, accéder à une variable “globale”, afficher un log...
84. No side effect
● Fonctions plus faciles à comprendre et à composer
● Possibilité de construire des choses complexes à partir d’éléments simples
● Local reasoning
86. ● Lancer une exception ?
No side effect
Renvoyer un Type d’erreur :
● Option[A] : un type ou pas
● Try[A] : un type ou un Throwable
● Either[A, B] : un type ou un autre
● Validation[A, Seq[ValidationError]] : un type ou une liste d’erreur
87. ● Lancer une exception ?
● Accès à une base de données ?
No side effect
88. ● Lancer une exception ?
● Accès à une base de données ?
No side effect
Effet de bord fait :
● en “bordure du système”
● idéalement par une librairie
● représenté par un type (Future, IO…)
Ex : def getDBUser(id: UserId): Future[Option[User]] = ???
89. ● Lancer une exception ?
● Accès à une base de données ?
● Afficher un log ?
No side effect
90. ● Lancer une exception ?
● Accès à une base de données ?
● Afficher un log ?
No side effect
On peut éventuellement se permettre un peu de liberté...
95. 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’
● Either/Try plutôt qu’une exception
● Collection API / recursivité plutôt que boucles for/while
● Eviter les ‘if’ autant que possible
● Séparation métier / technique
96.
97. Références
Does the Language You Use Make a Difference ?
When DDD meets FP, good things happen
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!