FP is coming... le 19/05/2016
-
Upload
loic-knuchel -
Category
Software
-
view
217 -
download
0
Transcript of FP is coming... le 19/05/2016
Loïc Knuchel
Freelance
Développeur web full-stack
Entrepreneur
Cookers / SalooN
@loicknuchel
http://loic.knuchel.org/
Geek passionné
Front-end
Fonctions pures
Immutabilité
Fonctions pures
ImmutabilitéModifier une variable ?
Accès BDD ?
Logs ? Exceptions ?
CRUD ?
Do not fear FP
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
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
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
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]
Transformer un tableau
function toUpperCase(list){ return list.map(function(item){ return item.toUpperCase(); });}
var names = ['Finn', 'Rey', 'Poe'];
console.log(toUpperCase(names));// ['FINN', 'REY', 'POE']
def toUpperCase(list: List[String]): List[String] = list.map(item => item.toUpperCase)
val names = List("Finn", "Rey", "Poe")
println(toUpperCase(names))// List(FINN, REY, POE)
Transformer un tableau
function toUpperCase(list){ return list.map(function(item){ return item.toUpperCase(); });}
var names = ['Finn', 'Rey', 'Poe'];
console.log(toUpperCase(names));// ['FINN', 'REY', 'POE']
def toUpperCase(list: List[String]): List[String] = list.map(_.toUpperCase)
val names = List("Finn", "Rey", "Poe")
println(toUpperCase(names))// List(FINN, REY, POE)
Créer son .map()
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;};
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();});
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ériqueHaut niveau d’abstraction
Un maximum de libraires externe
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ériqueHaut niveau d’abstraction
Un maximum de libraires externe
Concis / ExpressifFocalisé sur le domaine
Un minimum de libraires externe
Architecture Hexagonale
Manipuler des donnéesvar data = [{ id: '123', actions: [ {name: 'sendPicture', pictures: [ {path: '123/1.jpg', deleted: true, sync: false}, {path: '123/2.jpg', deleted: false, sync: true}, ]}, {name: 'sendPicture', pictures: [ {path: '123/3.jpg', deleted: false, sync: true}, {path: '123/4.jpg', deleted: false, sync: true} ]}, {name: 'sendPicture', pictures: [ {path: '123/5.jpg', deleted: true, sync: false}, {path: '123/6.jpg', deleted: false, sync: false} ]} ]}, { id: '456', actions: [ {name: 'sendPicture', pictures: [ {path: '456/1.jpg', deleted: false, sync: true}, {path: '456/2.jpg', deleted: false, sync: true}, ]}, {name: 'sendPicture', pictures: [ {path: '123/3.jpg', deleted: true, sync: false}, {path: '123/4.jpg', deleted: true, sync: false} ]} ]}];
function doSomething(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;}
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éespublic 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;}
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éespublic 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 !
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éespublic 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 idFilter actions by nameFilter pictures not deleted
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éespublic 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 idFilter actions by nameFilter pictures not deleted
Level up your abstraction !
Manipuler des données// Finds the 1st elt of the sequence satisfying a predicate, if anydef find[A](p: (A) => Boolean): Option[A]
// Selects all elts of this collection which satisfy a predicatedef filter[A](p: (A) => Boolean): List[A]
// Builds a new list by applying a function to all elements of the listdef map[A, B](f: (A) => B): List[B]
// Applies a binary operator to all elts of this listdef reduce[A, B](f: (B, A) => B, b: B): B
Manipuler des donnéesfunction getPictures(items, id){ return items .find(function(item){ return item.id === id; }).actions .filter(function(action){ return action.name === 'sendPicture'; }) .map(function(action){ return action.pictures; }) .reduce(function(a, b){ return a.concat(b); }, []) .filter(function(picture){ return !picture.deleted; });}
def getPictures(items: List[Item], id: String): List[Picture] = items .find(_.id == id) .map(_.actions).getOrElse(List()) .filter(_.name == "sendPicture") .flatMap(_.pictures) .filter(!_.deleted)
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;}
def getPictures(items: List[Item], id: String): List[Picture] = items .find(_.id == id) .map(_.actions).getOrElse(List()) .filter(_.name == "sendPicture") .flatMap(_.pictures) .filter(!_.deleted)
Manipuler des donnéesfunction 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;}
function getPictures(items, id){ return items .find(function(item){ return item.id === id; }).actions .filter(function(action){ return action.name === 'sendPicture'; }) .map(function(action){ return action.pictures; }) .reduce(function(a, b){ return a.concat(b); }, []) .filter(function(picture){ return !picture.deleted; });}
Safe ?
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;}
def getPictures(items: List[Item], id: String): List[Picture] = items .find(_.id == id) .map(_.actions).getOrElse(List()) .filter(_.name == "sendPicture") .flatMap(_.pictures) .filter(!_.deleted)
Manipuler des donnéesfunction 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;}
function getPictures(items, id){ return items .find(function(item){ return item.id === id; }).actions .filter(function(action){ return action.name === 'sendPicture'; }) .map(function(action){ return action.pictures; }) .reduce(function(a, b){ return a.concat(b); }, []) .filter(function(picture){ return !picture.deleted; });}
Cannot read property 'xxx' of
undefined x 6 !!!
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;}
def getPictures(items: List[Item], id: String): List[Picture] = items .find(_.id == id) .map(_.actions).getOrElse(List()) .filter(_.name == "sendPicture") .flatMap(_.pictures) .filter(!_.deleted)
Manipuler des donnéesfunction 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;}
function getPictures(items, id){ return items .find(function(item){ return item.id === id; }).actions .filter(function(action){ return action.name === 'sendPicture'; }) .map(function(action){ return action.pictures; }) .reduce(function(a, b){ return a.concat(b); }, []) .filter(function(picture){ return !picture.deleted; });}
Cannot read property 'xxx' of
undefined x 6 !!!
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;}
def getPictures(items: List[Item], id: String): List[Picture] = items .find(_.id == id) .map(_.actions).getOrElse(List()) .filter(_.name == "sendPicture") .flatMap(_.pictures) .filter(!_.deleted)
Manipuler des donnéesfunction 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;}
function getPictures(items, id){ return items .find(function(item){ return item.id === id; }).actions .filter(function(action){ return action.name === 'sendPicture'; }) .map(function(action){ return action.pictures; }) .reduce(function(a, b){ return a.concat(b); }, []) .filter(function(picture){ return !picture.deleted; });}
Cannot read property 'xxx' of
undefined x 6 !!!java.lang.NullPointerException x 6 !!!
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;}
def getPictures(items: List[Item], id: String): List[Picture] = items .find(_.id == id) .map(_.actions).getOrElse(List()) .filter(_.name == "sendPicture") .flatMap(_.pictures) .filter(!_.deleted)
Manipuler des donnéesfunction 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;}
function getPictures(items, id){ return items .find(function(item){ return item.id === id; }).actions .filter(function(action){ return action.name === 'sendPicture'; }) .map(function(action){ return action.pictures; }) .reduce(function(a, b){ return a.concat(b); }, []) .filter(function(picture){ return !picture.deleted; });}
Cannot read property 'xxx' of
undefined x 6 !!!java.lang.NullPointerException x 6 !!!
Safe code \o/
Java “Safe”public List<Picture> getPictures(List<Item> items, String id) { List<Picture> pictures = new ArrayList<>(); if (items != null) { for (Item item : items) { if (item != null && item.getId() == id && item.getActions() != null) { for (Action action : item.getActions()) { if (action != null && action.getName() == "sendPicture" && action.getPictures() != null) { for (Picture picture : action.getPictures()) { if (picture != null && !picture.getDeleted()) { pictures.add(picture); } } } } } } } return pictures;}
def getPictures(items: List[Item], id: String): List[Picture] = items .find(_.id == id) .map(_.actions).getOrElse(List()) .filter(_.name == "sendPicture") .flatMap(_.pictures) .filter(!_.deleted)
Option
Le problèmefunction getName(user) { return user.name;}
Le problèmefunction getName(user) { return user.name;}
public String getName(User user) { return user.getName();}
Le problèmefunction getName(user) { return user.name;}
public String getName(User user) { return user.getName();}
def getUser(user: User): String = user.name
Le problèmefunction getName(user) { return user.name;}
getName();// Cannot read property 'name' of undefined
public String getName(User user) { return user.getName();}
getName(null);// java.lang.NullPointerException
def getUser(user: User): String = user.name
// no null (used) in scala !
Le problèmefunction getName(user) { return user.name;}
getName();// Cannot read property 'name' of undefined
getName(localStorage.getItem('user'));// ERROR ???
public String getName(User user) { return user.getName();}
getName(null);// java.lang.NullPointerException
getName(getUser());// ERROR ???
def getUser(user: User): String = user.name
// no null (used) in scala !
Le problèmefunction getName(user) { return user.name;}
getName();// Cannot read property 'name' of undefined
getName(localStorage.getItem('user'));// ERROR ???
function getName(user) { return user ? user.name : '';}function getName(user) { return (user || {}).name;}
public String getName(User user) { return user.getName();}
getName(null);// java.lang.NullPointerException
getName(getUser());// ERROR ???
public String getName(User user) { if(user != null){ return user.getName(); } else { return ""; }}
def getUser(user: User): String = user.name
// no null (used) in scala !
Option
Le problèmefunction getName(user) { return user.name;}
getName();// Cannot read property 'name' of undefined
getName(localStorage.getItem('user'));// ERROR ???
function getName(user) { return user ? user.name : '';}
function getName(user) { return (user || {}).name;}
public String getName(User user) { return user.getName();}
getName(null);// java.lang.NullPointerException
getName(getUser());// ERROR ???
public String getName(User user) { if(user != null){ return user.getName(); } else { return ""; }}
def getUser(user: User): String = user.name
// no null (used) in scala !
def getUser(user: Option[User]): Option[String] = user.map(_.name)
def getUser(user: Option[User]): String = user.map(_.name).getOrElse("")
List.map() vs Option.map()
Monad
Monad
● Wrapper (context) M[A]
● Fonction map def map[B](f: A => B): M[B]
● Fonction flatMap def flatMap[B](f: A => M[B]): M[B]
Monad
● List
● Option
● Future
● Try
● ...
Basics
Typage fort
Typage fort
● Filet de sécurité pour garantir la cohérence du programme
Typage fort
● Filet de sécurité pour garantir la cohérence du programme
● Documentation pour le développeur
Typage fort
● Filet de sécurité pour garantir la cohérence du programme
● Documentation pour le développeur
● Implémentent certains concepts
Typage fort
● Filet de sécurité pour garantir la cohérence du programme
● Documentation pour le développeur
● Implémentent certains concepts
● null => Option
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
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● ...
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
Type all the things !!!case class Contact(
firstName: String, middleInitial: String, lastName: String,
emailAddress: String, isEmailVerified: Boolean
)
Type all the things !!!case class Contact(
firstName: String, middleInitial: String, lastName: String,
emailAddress: String, isEmailVerified: Boolean
)
Optionnel ?
Type all the things !!!case class Contact(
firstName: String, middleInitial: String, lastName: String,
emailAddress: String, isEmailVerified: Boolean
)
Optionnel ?Contrainte ?
Type all the things !!!case class Contact(
firstName: String, middleInitial: String, lastName: String,
emailAddress: String, isEmailVerified: Boolean
)
Optionnel ?Contrainte ?
Lien ?
Type all the things !!!case class Contact(
firstName: String, middleInitial: String, lastName: String,
emailAddress: String, isEmailVerified: Boolean
)
Optionnel ?Contrainte ?
Lien ?
Logique métier ?
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 ?
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 EmailAddresscase class VerifiedEmail(value: Email) extends EmailAddresscase class UnverifiedEmail(value: Email) extends EmailAddress
Optionnel ?Contrainte ?
Lien ?
Logique métier ?
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 EmailAddresscase class VerifiedEmail(value: Email) extends EmailAddresscase 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 ?
Stateless
Stateless
Passer toute les données nécessaires à chaque fois
Stateless
Passer toute les données nécessaires à chaque fois
● Testabilité
Stateless
Passer toute les données nécessaires à chaque fois
● Testabilité
● Plus facile à comprendre
Immutabilité
Immutabilité
Immutabilité
● Scalabilité / Multithreading
Immutabilité
● Scalabilité / Multithreading
● Meilleur nommage
Immutabilité
● Scalabilité / Multithreading
● Meilleur nommage
● Séparation données / calculs
Types et Fonctions plutôt que Classes :
● Entity● Value Object● Service
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}
No side effectEffet 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...
No side effect
No side effect
● Fonctions plus faciles à comprendre et à composer
● Possibilité de construire des choses complexes à partir d’éléments simples
● Local reasoning
● Lancer une exception ?
No side effect
● 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
● Lancer une exception ?● Accès à une base de données ?
No side effect
● 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]] = ???
● Lancer une exception ?● Accès à une base de données ?● Afficher un log ?
No side effect
● 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é...
Architecture Hexagonale
strict FPsoft FP
“Easy to learn/write”vs
“Easy to maintain”
DDD
Hexagonal architecture
Event Storming
Property based testing
Event Sourcing
Clean code
TDDBDD
Craftsmanship
Living Documentation
CQRS
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
RéférencesDoes 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!
[email protected] @loicknuchel http://loic.knuchel.org/