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

Loïc Knuchel
Loïc KnuchelScala tech lead at Gospeak
FP is coming...
@loicknuchel
Loïc Knuchel
Freelance
Développeur web full-stack
Entrepreneur
Cookers / SalooN
loicknuchel@gmail.com
@loicknuchel
http://loic.knuchel.org/
Geek passionné
FP is coming... le 19/05/2016
FP is coming... le 19/05/2016
FP
Front-end
FP is coming... le 19/05/2016
FP is coming... le 19/05/2016
FP is coming... le 19/05/2016
FP is coming... le 19/05/2016
FP is coming... le 19/05/2016
FP is coming... le 19/05/2016
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
FP is coming... le 19/05/2016
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érique
Haut 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érique
Haut niveau d’abstraction
Un maximum de libraires externe
Concis / Expressif
Focalisé sur le domaine
Un minimum de libraires externe
Architecture Hexagonale
Manipuler des données
var 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é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;
}
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 !
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
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 !
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
Manipuler des données
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; });
}
def getPictures(items: List[Item], id: String): List[Picture] =
items
.find(_.id == id)
.map(_.actions).getOrElse(List())
.filter(_.name == "sendPicture")
.flatMap(_.pictures)
.filter(!_.deleted)
FP is coming... le 19/05/2016
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ées
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;
}
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ées
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;
}
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ées
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;
}
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ées
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;
}
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ées
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;
}
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ème
function getName(user) {
return user.name;
}
Le problème
function getName(user) {
return user.name;
}
public String getName(User user) {
return user.getName();
}
Le problème
function getName(user) {
return user.name;
}
public String getName(User user) {
return user.getName();
}
def getUser(user: User): String = user.name
Le problème
function 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ème
function 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ème
function 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ème
function 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 EmailAddress
case class VerifiedEmail(value: Email) extends EmailAddress
case 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 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 ?
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 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...
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 FP
soft FP
“Easy to learn/write”
vs
“Easy to maintain”
FP is coming... le 19/05/2016
DDD
Hexagonal architecture
Event Storming
Property based testing
Event Sourcing
Clean code
TDD
BDD
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
FP is coming... le 19/05/2016
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!
loicknuchel@gmail.com @loicknuchel http://loic.knuchel.org/
1 of 98

Recommended

聞いてスッキリ!Lightningの理解ポイント by
聞いてスッキリ!Lightningの理解ポイント聞いてスッキリ!Lightningの理解ポイント
聞いてスッキリ!Lightningの理解ポイント寛 吉田
1.7K views22 slides
Aller plus loin avec Doctrine2 by
Aller plus loin avec Doctrine2Aller plus loin avec Doctrine2
Aller plus loin avec Doctrine2André Tapia
169 views108 slides
Program project khusus by
Program project khususProgram project khusus
Program project khususkiuntoro
194 views102 slides
F jxlo06xw by
F jxlo06xwF jxlo06xw
F jxlo06xwSorocaba City Halll
165 views2 slides
1- Sourcecode Array by
1- Sourcecode Array1- Sourcecode Array
1- Sourcecode ArrayFajar Baskoro
651 views13 slides
デバッグ戦略 by
デバッグ戦略デバッグ戦略
デバッグ戦略Masahiro Wakame
2.1K views39 slides

More Related Content

Viewers also liked

Gouvernernance des SI IMAT by
Gouvernernance des SI IMATGouvernernance des SI IMAT
Gouvernernance des SI IMATmoussadiom
1K views14 slides
création de la valeur by
création de la valeurcréation de la valeur
création de la valeurmoussadiom
1.1K views13 slides
The Easy-Peasy-Lemon-Squeezy, Statically-Typed, Purely Functional Programming... by
The Easy-Peasy-Lemon-Squeezy, Statically-Typed, Purely Functional Programming...The Easy-Peasy-Lemon-Squeezy, Statically-Typed, Purely Functional Programming...
The Easy-Peasy-Lemon-Squeezy, Statically-Typed, Purely Functional Programming...John De Goes
1.6K views107 slides
MTL Versus Free by
MTL Versus FreeMTL Versus Free
MTL Versus FreeJohn De Goes
2.2K views29 slides
Purify your Lambdas by
Purify your LambdasPurify your Lambdas
Purify your LambdasLuis Ángel Vicente Sánchez
1.2K views60 slides
Vers une nouvelle gouvernance des SI by
Vers une nouvelle gouvernance des SIVers une nouvelle gouvernance des SI
Vers une nouvelle gouvernance des SIAntoine Vigneron
1.4K views16 slides

Viewers also liked(20)

Gouvernernance des SI IMAT by moussadiom
Gouvernernance des SI IMATGouvernernance des SI IMAT
Gouvernernance des SI IMAT
moussadiom1K views
création de la valeur by moussadiom
création de la valeurcréation de la valeur
création de la valeur
moussadiom1.1K views
The Easy-Peasy-Lemon-Squeezy, Statically-Typed, Purely Functional Programming... by John De Goes
The Easy-Peasy-Lemon-Squeezy, Statically-Typed, Purely Functional Programming...The Easy-Peasy-Lemon-Squeezy, Statically-Typed, Purely Functional Programming...
The Easy-Peasy-Lemon-Squeezy, Statically-Typed, Purely Functional Programming...
John De Goes1.6K views
Vers une nouvelle gouvernance des SI by Antoine Vigneron
Vers une nouvelle gouvernance des SIVers une nouvelle gouvernance des SI
Vers une nouvelle gouvernance des SI
Antoine Vigneron1.4K views
“Going bananas with recursion schemes for fixed point data types” by Pawel Szulc
“Going bananas with recursion schemes for fixed point data types”“Going bananas with recursion schemes for fixed point data types”
“Going bananas with recursion schemes for fixed point data types”
Pawel Szulc1.4K views
Streams for (Co)Free! by John De Goes
Streams for (Co)Free!Streams for (Co)Free!
Streams for (Co)Free!
John De Goes2.1K views
Gouvernance du SI en pratique by nodesway
Gouvernance du SI en pratiqueGouvernance du SI en pratique
Gouvernance du SI en pratique
nodesway5.2K views
Post-Free: Life After Free Monads by John De Goes
Post-Free: Life After Free MonadsPost-Free: Life After Free Monads
Post-Free: Life After Free Monads
John De Goes3.8K views
Reducing Boilerplate and Combining Effects: A Monad Transformer Example by Connie Chen
Reducing Boilerplate and Combining Effects: A Monad Transformer ExampleReducing Boilerplate and Combining Effects: A Monad Transformer Example
Reducing Boilerplate and Combining Effects: A Monad Transformer Example
Connie Chen2.5K views
Make your programs Free by Pawel Szulc
Make your programs FreeMake your programs Free
Make your programs Free
Pawel Szulc3.9K views
7 key recipes for data engineering by univalence
7 key recipes for data engineering7 key recipes for data engineering
7 key recipes for data engineering
univalence 3.2K views
Scala Warrior and type-safe front-end development with Scala.js by takezoe
Scala Warrior and type-safe front-end development with Scala.jsScala Warrior and type-safe front-end development with Scala.js
Scala Warrior and type-safe front-end development with Scala.js
takezoe9.3K views
Urba ea topo-puits-v4extrait by René MANDEL
Urba ea topo-puits-v4extraitUrba ea topo-puits-v4extrait
Urba ea topo-puits-v4extrait
René MANDEL2K views
Principe du Puits de données pour un SI simple, agile, anticipant les Big Data by René MANDEL
Principe du Puits de données pour un SI simple, agile, anticipant les Big DataPrincipe du Puits de données pour un SI simple, agile, anticipant les Big Data
Principe du Puits de données pour un SI simple, agile, anticipant les Big Data
René MANDEL6.5K views

More from Loïc Knuchel

Scala bad practices, scala.io 2019 by
Scala bad practices, scala.io 2019Scala bad practices, scala.io 2019
Scala bad practices, scala.io 2019Loïc Knuchel
803 views22 slides
Mutation testing, enfin une bonne mesure de la qualité des tests ?, RivieraDe... by
Mutation testing, enfin une bonne mesure de la qualité des tests ?, RivieraDe...Mutation testing, enfin une bonne mesure de la qualité des tests ?, RivieraDe...
Mutation testing, enfin une bonne mesure de la qualité des tests ?, RivieraDe...Loïc Knuchel
1.7K views43 slides
Comprendre la programmation fonctionnelle, Blend Web Mix le 02/11/2016 by
Comprendre la programmation fonctionnelle, Blend Web Mix le 02/11/2016Comprendre la programmation fonctionnelle, Blend Web Mix le 02/11/2016
Comprendre la programmation fonctionnelle, Blend Web Mix le 02/11/2016Loïc Knuchel
962 views63 slides
Ionic2, les développeurs web à l'assaut du mobile, BDX I/O le 21/10/2016 by
Ionic2, les développeurs web à l'assaut du mobile, BDX I/O le 21/10/2016Ionic2, les développeurs web à l'assaut du mobile, BDX I/O le 21/10/2016
Ionic2, les développeurs web à l'assaut du mobile, BDX I/O le 21/10/2016Loïc Knuchel
504 views106 slides
Ionic2 - the raise of web developer, Riviera DEV le 17/06/2016 by
Ionic2 - the raise of web developer, Riviera DEV le 17/06/2016Ionic2 - the raise of web developer, Riviera DEV le 17/06/2016
Ionic2 - the raise of web developer, Riviera DEV le 17/06/2016Loïc Knuchel
705 views71 slides
Programmation fonctionnelle en JavaScript by
Programmation fonctionnelle en JavaScriptProgrammation fonctionnelle en JavaScript
Programmation fonctionnelle en JavaScriptLoïc Knuchel
1.2K views88 slides

More from Loïc Knuchel(14)

Scala bad practices, scala.io 2019 by Loïc Knuchel
Scala bad practices, scala.io 2019Scala bad practices, scala.io 2019
Scala bad practices, scala.io 2019
Loïc Knuchel803 views
Mutation testing, enfin une bonne mesure de la qualité des tests ?, RivieraDe... by Loïc Knuchel
Mutation testing, enfin une bonne mesure de la qualité des tests ?, RivieraDe...Mutation testing, enfin une bonne mesure de la qualité des tests ?, RivieraDe...
Mutation testing, enfin une bonne mesure de la qualité des tests ?, RivieraDe...
Loïc Knuchel1.7K views
Comprendre la programmation fonctionnelle, Blend Web Mix le 02/11/2016 by Loïc Knuchel
Comprendre la programmation fonctionnelle, Blend Web Mix le 02/11/2016Comprendre la programmation fonctionnelle, Blend Web Mix le 02/11/2016
Comprendre la programmation fonctionnelle, Blend Web Mix le 02/11/2016
Loïc Knuchel962 views
Ionic2, les développeurs web à l'assaut du mobile, BDX I/O le 21/10/2016 by Loïc Knuchel
Ionic2, les développeurs web à l'assaut du mobile, BDX I/O le 21/10/2016Ionic2, les développeurs web à l'assaut du mobile, BDX I/O le 21/10/2016
Ionic2, les développeurs web à l'assaut du mobile, BDX I/O le 21/10/2016
Loïc Knuchel504 views
Ionic2 - the raise of web developer, Riviera DEV le 17/06/2016 by Loïc Knuchel
Ionic2 - the raise of web developer, Riviera DEV le 17/06/2016Ionic2 - the raise of web developer, Riviera DEV le 17/06/2016
Ionic2 - the raise of web developer, Riviera DEV le 17/06/2016
Loïc Knuchel705 views
Programmation fonctionnelle en JavaScript by Loïc Knuchel
Programmation fonctionnelle en JavaScriptProgrammation fonctionnelle en JavaScript
Programmation fonctionnelle en JavaScript
Loïc Knuchel1.2K views
Ionic Framework, L'avenir du mobile sera hybride, bdx.io le 16-10-2015 by Loïc Knuchel
Ionic Framework, L'avenir du mobile sera hybride, bdx.io le 16-10-2015Ionic Framework, L'avenir du mobile sera hybride, bdx.io le 16-10-2015
Ionic Framework, L'avenir du mobile sera hybride, bdx.io le 16-10-2015
Loïc Knuchel2.8K views
Ionic, ce n'est pas que de l'UI, meetup PhoneGap le 25-05-2015 by Loïc Knuchel
Ionic, ce n'est pas que de l'UI, meetup PhoneGap le 25-05-2015Ionic, ce n'est pas que de l'UI, meetup PhoneGap le 25-05-2015
Ionic, ce n'est pas que de l'UI, meetup PhoneGap le 25-05-2015
Loïc Knuchel1.6K views
Le développement mobile hybride sort du bois, Ch'ti JUG le 15-04-2015 by Loïc Knuchel
Le développement mobile hybride sort du bois, Ch'ti JUG le 15-04-2015Le développement mobile hybride sort du bois, Ch'ti JUG le 15-04-2015
Le développement mobile hybride sort du bois, Ch'ti JUG le 15-04-2015
Loïc Knuchel3.4K views
Devoxx 2015, Atelier Ionic - 09/04/2015 by Loïc Knuchel
Devoxx 2015, Atelier Ionic - 09/04/2015Devoxx 2015, Atelier Ionic - 09/04/2015
Devoxx 2015, Atelier Ionic - 09/04/2015
Loïc Knuchel3.4K views
Devoxx 2015, ionic chat by Loïc Knuchel
Devoxx 2015, ionic chatDevoxx 2015, ionic chat
Devoxx 2015, ionic chat
Loïc Knuchel2.5K views
Ionic HumanTalks - 11/03/2015 by Loïc Knuchel
Ionic HumanTalks - 11/03/2015Ionic HumanTalks - 11/03/2015
Ionic HumanTalks - 11/03/2015
Loïc Knuchel1.7K views
Ionic bbl le 19 février 2015 by Loïc Knuchel
Ionic bbl le 19 février 2015Ionic bbl le 19 février 2015
Ionic bbl le 19 février 2015
Loïc Knuchel5.7K views
Des maths et des recommandations - Devoxx 2014 by Loïc Knuchel
Des maths et des recommandations - Devoxx 2014Des maths et des recommandations - Devoxx 2014
Des maths et des recommandations - Devoxx 2014
Loïc Knuchel1.2K views

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

  • 2. Loïc Knuchel Freelance Développeur web full-stack Entrepreneur Cookers / SalooN loicknuchel@gmail.com @loicknuchel http://loic.knuchel.org/ Geek passionné
  • 5. FP
  • 14. Fonctions pures Immutabilité Modifier une variable ? Accès BDD ? Logs ? Exceptions ? CRUD ?
  • 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
  • 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]
  • 21. 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)
  • 22. 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)
  • 23. 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; };
  • 24. 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(); });
  • 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
  • 28. Manipuler des données var 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; }
  • 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
  • 34. Manipuler des données 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; }); } def getPictures(items: List[Item], id: String): List[Picture] = items .find(_.id == id) .map(_.actions).getOrElse(List()) .filter(_.name == "sendPicture") .flatMap(_.pictures) .filter(!_.deleted)
  • 36. 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ées 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; } 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 ?
  • 37. 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ées 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; } 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 !!!
  • 38. 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ées 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; } 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 !!!
  • 39. 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ées 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; } 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 !!!
  • 40. 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ées 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; } 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/
  • 41. 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)
  • 43. Le problème function getName(user) { return user.name; }
  • 44. Le problème function getName(user) { return user.name; } public String getName(User user) { return user.getName(); }
  • 45. Le problème function getName(user) { return user.name; } public String getName(User user) { return user.getName(); } def getUser(user: User): String = user.name
  • 46. Le problème function 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 !
  • 47. Le problème function 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 !
  • 48. Le problème function 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 !
  • 50. Le problème function 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("")
  • 52. Monad
  • 53. 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]
  • 54. Monad ● List ● Option ● Future ● Try ● ...
  • 57. Typage fort ● Filet de sécurité pour garantir la cohérence du programme
  • 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 ?
  • 73. Stateless Passer toute les données nécessaires à chaque fois
  • 74. Stateless Passer toute les données nécessaires à chaque fois ● Testabilité
  • 75. Stateless Passer toute les données nécessaires à chaque fois ● Testabilité ● Plus facile à comprendre
  • 79. Immutabilité ● Scalabilité / Multithreading ● Meilleur nommage
  • 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
  • 85. ● Lancer une exception ? No side effect
  • 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é...
  • 94. DDD Hexagonal architecture Event Storming Property based testing Event Sourcing Clean code TDD BDD Craftsmanship Living Documentation CQRS
  • 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
  • 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!