Password     hola#1234

SSID          devoxxfr-hol

fp-ts

Quand TypeScript devient

fonctionnel

Jordane Grenat

Johan Rouve

Bastien Tran

Agenda

13h30 - Introduction

13h45 - Exercices - tome 1

15h00 - Pause

15h15 - Exercices - tome 2

Programmation fonctionnelle ?

  • Fonctions
  • Pureté
  • Immutabilité
const todoList = [
  { task: 'Give a talk at Devoxx' }
];

const addTodo = (task) => {
  todoList.push({ task: task });
};

addTodo('Visit Canada');
const todoList = [
  { task: 'Give a talk at Devoxx' }
];

const addTodo = (task, todoList) => {
  return [...todoList, { task: task }];
};

const updatedList = addTodo('Visit Canada', todoList);
const sendEmail = (user) => {
    if (user.email === null) {
        throw new Error('No email provided for the user');
    }    
    launchNuclearMissile();
}

Honnêteté

const names = ['Johan', 'Bastien', 'Jordane'];

const uppercaseNames = [];

for (const name of names) {
  uppercaseNames.push(name.toUppercase());
}
const names = ['Johan', 'Bastien', 'Jordane'];

const toUppercase = name => name.toUppercase();

const uppercaseNames = names.map(toUppercase);

Outils

  • fonctions
  • map, filter, reduce
  • Functor
  • Applicative
  • Monad
const log = (level, prefix, message) => {
  // ... 
};

log('error', 'Authentication', 'Bad login/password');
const log = level => prefix => message => {
  // ... 
};

const logError = log('error');
const logAuthenticationError = logError('Authentication');

logAuthenticationError('Bad login/password');
tryLogin(login, password)
  .onError(logAuthenticationError);
tryLogin(login, password)
  .onError(error => log('error', 'Authentication', error));
const filter = (predicate) => (array) => //...

const getActiveUsers = filter(user => user.status === 'active');
const activeUsers = getActiveUsers(allUsers);

const getFruits = filter(item => item.type === 'fruit');
const allFruits = getFruits(allItems);
const double = x => x * 2;
const add = x => y => x + y;

const result = add(4)(double(3)); // 10
const result = 3
	|> double
	|> add(4)
(3)
(6)
= 6

Pipe

const filter = (predicate) => (array) => //...
const groupBy = (accessor) => (array) => // ...
 
const users = [
  { name: 'Marcia', city: 'Paris', hobby: 'music' },
  { name: 'Nicolas', age: 'Tours', hobby: 'music' },
  { name: 'Camille', age: 'Paris', hobby: 'theater' }
];
const byHobby = users
	|> filter(user => user.city === 'Paris')
	|> groupBy(user => user.hobby);
const parisians = filter(user => user.city === 'Paris')(users);
const byHobby = groupBy(user => user.hobby)(parisians);

// {
// 	 music: [{ name: 'Marcia', city: 'Paris', hobby: 'music' }], 
// 	 theater: [{ name: 'Camille', age: 'Paris', hobby: 'theater' }] 
// }

fp-ts

Disclaimer. Teaching functional programming is out of scope of this project, so the documentation assumes you already know what FP is.

It includes the most popular data types, type classes, and abstractions from languages like Haskell, PureScript, and Scala.

const findUser = (userId) => {
  return db.query(`SELECT * FROM users WHERE userId = ${userId}`);
}

Et les types ?

const findUser = (userId: number): Promise<User | undefined> => {
  return db.query(`SELECT * FROM users WHERE userId = ${userId}`);
}
const filter = (predicate) => (array) => //...
const groupBy = (accessor) => (array) => // ...

const byHobby = users
	|> filter(user => user.city === 'Paris')
	|> groupBy(user => user.hobby);
import * as A from 'fp-ts/Array';
import * as NEA from 'fp-ts/NonEmptyArray';

const byHobby = users
	|> A.filter(user => user.city === 'Paris')
	|> NEA.groupBy(user => user.hobby);
import { pipe } from 'fp-ts/function';
import * as A from 'fp-ts/Array';
import * as NEA from 'fp-ts/NonEmptyArray';

const byHobby = pipe(
  users,
  A.filter(user => user.city === 'Paris'),
  NEA.groupBy(user => user.hobby)
);
import { pipe } from 'fp-ts/function';
import * as A from 'fp-ts/Array';
import * as NEA from 'fp-ts/NonEmptyArray';

Normes d'import

Modules

  • Array
  • NonEmptyArray
  • Option
  • Either
  • Record
  • Task
  • Reader
  • ...

Option

const findUser = (userId: number): User | undefined => {
  // ...
};
const findUser = (userId: number): Option<User> => {
  // ...
};
type Option<A> = 
  | { _tag: 'Some', value: A } 
  | { _tag: 'None' };
import * as O from 'fp-ts/Option';

const username = pipe(
  userId,
  findUser,
  O.map(user => user.name)
);

Tous en boite !

Option : une boîte contenant zéro ou une valeur

Array : une boîte contenant zéro, une ou plusieurs valeurs du même type

NonEmptyArray : une boîte contenant une ou plusieurs valeurs du même type

map : transformer ce qu'il y a dans la boite

  1. Cloner le repository
  2. npm install
  3. npm run test
  4. Réactiver les test un à un
  5. Les faire passer au vert

Password     hola#1234

SSID          devoxxfr-hol

Une boîte contenant une valeur d'un type ou d'un autre

Either

type Either<E, A> = Left<E> | Right<A>

Très souvent, le Either représente une erreur : soit tout est Right, soit on a une erreur en Left.

Une boîte qui contient ou contiendra une valeur

Task

type Task<A> = () => Promise<A>

Une Task ne peut pas échouer

Une boîte qui contient ou contiendra une valeur d'un type E ou d'un type A

TaskEither

type TaskEither<E, A> = () => <Either<E, A>>

Have

a

break !

À Gagner

Wallaby (2)

TDD lovers

Quokka (2)

IDE sandbox

Console ninja (2)

Console addicts

À l'origine, il y eut le Magma

Un ensemble avec une
loi de composition interne

Exemple : Ensemble des entiers naturels N

(N, +) est un magma, car pour tout a + b = c, c appartient aussi à N

Cette loi est une fonction, souvent appelée concat

Ça peut très vite devenir un Semi-groupe

Si la loi de composition est
associative

Exemple : 

(N, +) est un semi-groupe, a + (b + c) = (a + b) + c

Voire même un Monoïde

S'il existe un élément
neutre

Exemple : 

(N, +) est un monoïde, car 0 est l'élément neutre : 0 + a = a

On ne joue pas tous dans la même Catégorie

Et une Applicative ?

C'est une boite

Boîte M avec deux fonctions :

- unit : a => M a
- bind ou flatMap : (a => M b) => M a => M b

Mais Jamy, c'est quoi une
Monade ?

C'est une boite

Boîte M avec deux fonctions :

- unit : a => M a
- bind ou flatMap : (a => M b) => M a => M b

Workshop fp-ts

By ereold

Workshop fp-ts

  • 36