title theme highlightTheme revealOptions
Intro To Functional Programming
solarized
atom-one-dark
transition
slide

Une petite introduction au

Functional Programming

Slides: https://gvergnaud.github.io/intro-to-functional-programming


đź‘‹

Gabriel Vergnaud

HĂ©ticien de la P2017

Frontend engineer at 
[@gvergnaud](https://github.com/gvergnaud) on 
[@GabrielVergnaud](https://twitter.com/GabrielVergnaud) on 

Note:

  • Qui suis je ?
    • gabriel vergnaud
    • Heticien P2017
    • developer Ă  Sketchfab.com (On recrute!)
    • gvergnaud on github
    • GabrielVergnaud on twitter

đź‘€

Qui ĂŞtes vous ?

Note:

  • Qui etes vous ?
    • Techno utilisĂ©e ?
    • quels projets ?
    • plutot agence / produits ?
    • Dev back vs dev front ?

Objectif de cette semaine

Note: Quel genre de cours de dev j'avais besoin et envie quand j'étais en H3 ? appris en auto-didacte. Connaissances empirique Je faisais des applications de plus en plus complexe, mais il arrivé toujours un moment ou cette complexité devenait un peu hors de control. ça devenait compliqué d'ajouter des features sans créer des bugs, le code devenait difficile à lire...

J'avais envie d'approfondir mes connaissances pour éviter de tomber dans ces pièges. -> L'architecture d'application.

Au dela de react, a travers les quelques jours de cours que j'ai avec vous, j'ai envie de vous transmettre ce que j'ai découvert ces dernières années en matière d'architecture d'application.


J'étais sensé vous parler de React...

Mais d'abord...

Note:

J'ai beaucoup hésité, j'ai préparé un cours sur react au début et tout

Mais je me suis rendu compte que pour vous parler de ce dont je dois vous parler je dois introduire un certain nombre de concepts.


Un peu de théorie


Note:

ça fait peur, mais c'est cette partie science dans computer science qui fait qu'on fait un métier digne d'intérêt et que l'on est pas des machines. Il faut pas avoir peur de la théorie.


Une petite introduction au

Functional Programming

Slides: https://gvergnaud.github.io/intro-to-functional-programming


Objectifs đź—ş

  • Comprendre ce qui se cache sous le terme functional programming
  • Donner des pistes Ă  explorer

Pourquoi ?


De nouveaux frameworks tous les ans

Ceux des années précédentes deviennent obsolètes

Que doit on apprendre qui nous serve Ă  long terme ?


Solution

ArrĂŞter de passer du temps Ă  apprendre des **API**

Mieux comprendre les fondamentaux du langage

Se concentrer sur les **design patterns**


MPJME - How to stay relevant as a developer

[https://www.youtube.com/watch?v=ZZUY37RQS-k](https://www.youtube.com/watch?v=ZZUY37RQS-k)


la promesse du functional programming


un code avec

Moins de bug

parce que plus facile Ă  comprendre

en moins de temps

parce que plus facile à réutiliser


“Developers proficient in functional programming are going to be in large demand in the very near future.”

Eric Elliott - The Two pillars of Javascript - Part 2


Quelque langage functionnels

Lisp, ML,

Haskell, Scala, Erlang, Elixir, Clojure, OCaml…


Qui compilent en JS

Reason, Elm, ClojureScript


Dans une moindre mesure

le JS


Mais

on peut faire du fonctional dans presque tous les languages.

Ruby, Python, ..., mĂŞme PHP !


Ok mais c'est quoi ?


Préférer un style **déclaratif** à un style impératif

+

découper son code en petites **fonctions** réutilisables et composables.

+

Ne **jamais** mutter les données

Note: Ça permet de monter facilement en abstraction et d’arreter de tout micro manager.


I

Impératif vs Déclaratif


Filtrer un tableau

Impératif :

const users = [{ age: 20 }, { age: 31 }, { age: 17 }]

let oldUsers = []
for (let i = 0; i < users.length; i++) {
	const currentUser = users[i]
	if (currentUser.age > 30) oldUsers.push(currentUser)
}

console.log(oldUsers)
// => [{ age: 31 }]

Oulala qu'est ce que ça micromanage

Note:

Micro management =

  • dĂ©claration de variables temporaire,
  • for loops,
  • if statements

les symptomes de l'impératif

  • dĂ©claration de variables temporaires
  • mutation de donnĂ©es
  • des statements
    • for loops
    • if/else statements

Filtrer un tableau

DĂ©claratif :

const oldUsers = filter(user => user.age > 30, users)

console.log(oldUsers)
// => [{ age: 31 }]


Filtrer un tableau

DĂ©claratif :

const oldUsers = filter(user => user.age > 30, users)

console.log(oldUsers)
// => [{ age: 31 }]

On va décrire le comportement du programme

plutĂ´t que de dire ce qu'il se passe

Ă  chaque Ă©tape de la boucle.

Mais ou est donc définie la function filter ?


Le déclaratif est construit sur de l’impératif.

function filter(predicate, xs) {
	let out = []
	for (let i = 0; i < xs.length; i++) {
		if (predicate(xs[i])) out.push(xs[i])
	}
	return out
}

`filter` est ce qu'on appelle une **Higher Order Function**


Higher Order Function

“Its like the word Quintessential :

when you say it, it just makes you feel smart.”

@mpjme

Youtube - Higher Order Functions


Higher Order Function

Une fonction qui :

va **abstraire** un bout de code **générique**

va prendre une **autre** fonction en paramètre

qui **décrit** le comportement **spécifique** de notre code.


Je vous ai menti

(un peu)

On peut aussi faire des boucles de manière déclarative

const filter = (f, [x, ...rest]) =>
	rest.length
		? f(x) ? [x, ...filter(f, rest)] : filter(f, rest)
		: f(x) ? [x] : []

grâce à la recursion !


Filter est build-in sur Array.prototype

const oldUsers = users.filter(user => user.age > 30)

Transformer un tableau

C'est à dire transformer chacun des items d'un tableau pour en créer un nouveau.

Impératif :

const users = [{ age: 20 }, { age: 31 }, { age: 17 }]

let userAges = []
for (let i = 0; i < users.length; i++) {
	userAges.push(users[i].age)
}

console.log(userAges)
// => [20, 31, 17]

Transformer un tableau

Déclaratif : la méthode map()

const userAges = users.map(user => user.age)
console.log(userAges)
// => [20, 31, 17]

OU

const userAges = map(user => user.age, users)

console.log(userAges)
// => [20, 31, 17]

Transformer un tableau

implémentation de map

function map(mapper, xs) {
	let out = []
	for (let i = 0; i < xs.length; i++) {
		out.push(mapper(xs[i]))
	}
	return out
}

ou avec recursion

const map = (f, [x, ...rest]) =>
  rest.length ? [f(x), ...map(rest)] : [f(x)]

Accumuler un tableau

C'est Ă  dire transformer les items d'un tableau en une seule valeur.

Impératif :

const numbers = [20, 31, 17]

let sum = 0
for (let i = 0; i < numbers.length; i++) {
	sum = sum + numbers[i]
}

console.log(sum)
// => 68

Accumuler un tableau

Déclaratif : la méthode reduce()

const sum = numbers.reduce((acc, num) => acc + num, 0)

console.log(sum)
// => 68

OU

const sum = reduce((acc, num) => acc + num, 0, numbers)

console.log(sum)
// => 68

Accumuler un tableau

Implémentation de reduce

function reduce(reducer, seed, xs) {
  let out = seed
  for (let i = 0; i < xs.length; i++) {
    out = reducer(out, xs[i])
  }
  return out
}

Ou avec recursion

const reduce = (f, acc, [x, ...rest]) =>
  rest.length ? reduce(f, f(acc, x), rest) : f(acc, x)

Chainer ces modifications

C'est maintenant que ça prend tout son sens...


// users :: [ { name :: String, age :: Int } ]

const name = 'Kevin'
let averageAge = 0
let filteredUsers = []

for (let i = 0; i < users.length; i++) {
  const user = users[i]
  if (user.name === name) filteredUsers.push(user)
}

for (let j = 0; j < filteredUsers.length; j++) {
  const user = filteredUsers[j]
  averageAge = averageAge + user.age / filteredUsers.length
}

Qu'est ce que ça fait en un coup d'oeil ?



const averageAge = users
  .filter(user => user.name === 'Kevin')
  .map(user => user.age)
  .reduce((acc, age, _, ages) => acc + age / ages.length, 0)

Et maintenant ?


En conclusion

  • L'ImpĂ©ratif, visuellement, c'est pas facile.

la spécificité du bout de code est noyée dans les détails d'implémentation génériques.


En conclusion

  • ImpĂ©ratif = code peu rĂ©utilisable

nos boucles sont spécifiquement faite pour notre problème actuel.


En conclusion

  • ImpĂ©ratif = code complexe

Simplicity vs Complexity

Rich Hickey - [Simple Made Easy](https://www.youtube.com/watch?v=rI8tNMsozo0)


T.P.

Parser de la NASA !


<iframe src="https://codesandbox.io/embed/exercice-fp-declarative-vs-imperative-lrion?fontsize=14&view=editor" title="Exercice FP declarative vs imperative" allow="geolocation; microphone; camera; midi; vr; accelerometer; gyroscope; payment; ambient-light-sensor; encrypted-media" style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;" sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"></iframe>

II

Des petites functions réutilisables


fonctions pures et impures

Une fonction pure ne produit pas de side effect.
Une fonction pure retourne le mĂŞme resultat si on lui donne les mĂŞmes arguments.
Une fonction pure est une fonction au sens mathématique du terme.

Note:

  • Dans le monde de la programmation fonctionelle on appelle ça du code Impure
    • Une fonction pure n'a pas de side effect et donne le mĂŞme rĂ©sultat si on lui donne le arguments.
    • on dit qu'elle est rĂ©fĂ©rentiellement transparente (referencial transparency).

I hate maths


une fonction pure

prend des paramètres et retourne un résultat

// Pure
const add = (a, b) => a + b
add(2, 2)
// => 4
// Impure
const add = (a, b, cb) => {
  cb(a + b)
}
add(2, 2, v => console.log(v))
// => undefined
// "4"

une fonction pure

n'altère pas les paramètres qui lui sont passés

// Pure
const addValue = (arr, v) => arr.concat(v)
addValue([1, 2, 3], 4)
// => [1, 2, 3, 4]
// Impure
const addValue = (arr, v) => {
  arr.push(v)
}

let myArray = [1, 2, 3]
addValue(myArray, 4)
// => undefined
// myArray === [1, 2, 3, 4]

une fonction pure

ne produit pas de side effect

const number = 3

// Pure
const isOdd = x => !(x % 2)
isOdd(number)
// => true
// Impure
let isNumberOdd;
const isOdd = () => {
  isNumberOdd = !(number % 2)
}

isOdd() // => undefined
console.log(isNumberOdd) // => true

// Impure
const getUsers = (callback) => {
  fetch(`/user`).then(callback)
}

Pourquoi c'est chouette ?

On peut **tout comprendre** juste en regardant le contenu de la fonction
car elle ne dépend pas du monde extérieur.

Un comportement consistant, donc moins de bugs.

Puisqu'une fonction pure retourne le **mĂŞme resultat** si on lui donne les **mĂŞmes arguments**,
On peut **memoize** le resultat! **memoize** est une manière d'optimiser les performances en gardant le resultat en mémoire pour **éviter de ré-executer** le code si la fonction est appelée avec les **mêmes arguments**.

Ptit test


const isThisPure = x => x * 2

alors ? pure ou pas pure ?

Pure


const isThisPure = str => {
	window.title = str
}

Impure


const isThisPure = str => {
	const title = `AppName - ${str}`
	return () => window.title = title
}

Pure

Retourner une fonction impure sans l'exécuter ne rend pas la fonction impure.

“Mais avec ça on peut rien faire !”


Oui, c'est vrai.


Ce qui nous intéresse dans un programme c'est les side effects.

**Afficher** quelque chose sur la page

**RĂ©agir** Ă  des events

**Écrire** dans une base de données

On va pas loin avec des fonctions pures.


Mais les side effects sont aussi les plus susceptibles de bugger.


Flow d'un code functional

    SideEffect -> Pure -> SideEffect

Pousser les side effects aux **extrémités** de notre code.

Note:

Demo ->

getData().then(app)

const app = compose(
  render
  map(etc)
)

Par exemple :

    GetData -> Computation -> Render

Computation : la **logique** de notre app


const app = () => render(compute(getData()))

Ca vous rappelle pas quelque chose ?


const app = () => view(controller(model()))

On retrouve les trois parties du model **MVC**

les **données**, le **traitement** et la **vue**.


composition


Mais qu'est ce donc ?

Soit deux fonction f(x) et g(x)

leur function composée est f(g(x))


des maths :(


Mais non !


des legos ! :)


Je m'explique

L'idée est de connecter les functions entre elles

en passant le resultat d'un fonction en paramètre à la suivante.

import { compose } from 'ramda';

const double = x => x * 2
const tripple = x => x * 3

// composition ->

const sixtuple = compose(double, tripple)

console.log(sixtuple(7))
// 42

Pareil que

const double = x => x * 2
const tripple = x => x * 3

const sixtuple = x => double(tripple(x))

console.log(sixtuple(7))
// 42

Pareil que

const sixtuple = x => {
  const trippled = tripple(x)
  const result = double(trippled)
  return result
}

console.log(sixtuple(7))
// 42

Composition

Comme si on emboitait des légos !


“The way to control Complexity

is Compositionality.”

Brian Beckman: Don't Fear The Monad


(Parenthèse)

Hindley-Milner Type Notation


Hindley-Milner Type Notation

Signature de function qui décrit son type

  multiplyByTwo :: Int -> Int
  const multiplyByTwo = x => x * 2
_

Hindley-Milner Type Notation

  length :: String -> Int
  const length = str => str.length
_

Je l'ai pas inventé !

Il y a même un moteur de recherche pour ça :

HOOGLE HOOGLE

Haskell Function Search Engine


Hindley-Milner Type Notation

C'est très pratique pour composer des functions.

length :: String -> Int
const length = str => str.length

isGreaterThanTen :: Int -> Bool
const isGreaterThanTen = x => x > 10

quel sera la signature de

compose(isGreaterThanTen, length)

?


Hindley-Milner Type Notation

const isLengthGreaterThanTen = compose(length, isGreaterThanTen)
(String -> Int) + (Int -> Bool) = String -> Bool

donc

isLengthGreaterThanTen :: String -> Bool

la composition

Le problème

comment on fait quand les functions prènnent plusieurs arguments ?


curry

la solution Ă  tous nos soucis


Le Currying

une fonction curriée est une function qui, lorsqu'on ne lui donne pas tous ses paramètres, renvoie une function qui prendra le reste.


Le Currying

import { curry } from 'ramda';

const sumOfFour = (a, b, c, d) => a + b + c + d

sumOfFour(1)
// => NaN

const curriedSumOfFour = curry(sumOfFour)
curriedSumOfFour(1)
// => (b, c, d) => 1 + b + c + d
_

Le Currying

Presque pas besoin :

const sumOfFour = a => b => c => d => a + b + c + d

sumOfFour(1)
// => (b, c, d) => 1 + b + c + d
_

en ES6, les arrow functions facilitent la syntaxe

des functions qui retournent des functions.


Le Currying

Mais la function curry nous permet de passer

aussi plusieurs paramètres d'un coup si on veut :

const sumOfFour = curry((a, b, c, d) => a + b + c + d)

sumOfFour(1, 2)
// => (c, d) => 1 + 2 + c + d

const sumOfFour = a => b => c => d => a + b + c + d

sumOfFour(1, 2)
// => (b, c, d) => 1 + b + c + d
// :(
_

(Parenthèse)

Haskell Curry


T.P.

Back to the nasa


<iframe src="https://codesandbox.io/embed/exercice-fp-declarative-vs-imperative-46rr9?fontsize=14&view=editor" title="Exercice FP curry and compose" allow="geolocation; microphone; camera; midi; vr; accelerometer; gyroscope; payment; ambient-light-sensor; encrypted-media" style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;" sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"></iframe>

reprenons


Des données immutables


Des données immutables

En functional programming lorsque l'on assigne une variable, ça veut dire qu'elle ne changera plus jamais.


Des données immutables

Les données immutables c'est bien

parce ce que ça supprime de l'incertitude.


Incertitude = Bug


(Parenthèse)

l'utilisation de const ne rend pas la donnée immutable.

Cela empèche juste que la variable puisse être ré-assignée.

Ou pourra toujours modifier les items d'un array ou les clés d'un objet.


Des données immutables

Comment Ă©viter de mutter un tableau ?

ne pas utiliser push()

  const immutablePush = (x, xs) => xs.concat(x)
  // OU
  const immutablePush = (x, xs) => [ ...xs, x]
_

modifier un item :

  const immutableSetAtIndex = (index, value, xs) => [
    ...xs.slice(0, index),
    value,
    ...xs.slice(index + 1)
  ]
_

Des données immutables

Comment Ă©viter de mutter un object ?

  const objectSet = (key, value, obj) =>
    Object.assign({}, obj, { [key]: value })

  // OU

  const objectSet = (key, value, obj) =>
    ({ ...obj, [key]: value })
_

Des données immutables

Les lens

Une api plus friendly pour faire de l'immutable.

3 méthodes : view, over et set.

// view :: Lens -> DataStructure -> a
view(lens, dataStructure)

// over :: Lens -> (a -> a) -> DataStructure -> DataStructure
over(lens, mapper, dataStructure)

// set :: Lens -> a -> DataStructure -> DataStructure
set(lens, value, dataStructure)
_

Des données immutables

Immutable.js


BientĂ´t fini !

plus que Les Setoid,

Les Semigroup, Les Monoid, Les Functor,

Les Apply, Les Applicative, Les Foldable, Les Traversable,

Les Chain, Les Monad, Les Extend, Les Comonad ...


Merci

de votre attention


Ressources


Mon ptit nom c'est Gabriel Vergnaud

<style> .flex { display: flex; align-items: center; justify-content: center; } .flex > *:not(:first-child) { margin-left: 10px } img.simple-image.simple-image { border:none; box-shadow:none; background: none; } .white.white.white { color: white; text-shadow: 0 2px 4px rgba(0,0,0, .5); } .lower.lower.lower { text-transform: none; } .reveal { font-size: 32px; } .reveal small { font-size: 0.7em; line-height: 1.5; } .reveal pre { border-radius: 5px; box-shadow: 0px 8px 25px rgba(0,0,0,.25); } .reveal section img { border: none; box-shadow: 0 5px 15px rgba(0, 0, 0, 0.15) } .reveal pre code { padding: 30px; border-radius: 5px; font-weight: normal; } .reveal code { font-weight: bold; } </style>