FP is coming... le 19/05/2016

Post on 14-Apr-2017

217 views 0 download

Transcript of FP is coming... le 19/05/2016

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