Functional programming
Illustrated by Scala
def pure[F[_], A](x: => A): F[A]
Qui suis-je ? (vaste question)
@ugobourdon




- CTO Performance Immo
 - Programmeur indépendant
 
Mon boulot actuellement
Ce que j'aime faire
Agenda
- Un petit mot sur Scala
 - Programmation fonctionnelle : une définition
 - Type Algébrique de données
 - Théorie des catégories (a.k.a Monad realm)
 - Effets de bord & FP
 - Programmation générique (a.k.a paramétricité)
 


Troll n°1
Révélation !
Scala n'est pas un langage fonctionnel
Scala est un langage purement OBJET
    
        class ScalaIsAwesome(z: Option[String]) {
        
            val x: String = "functional programming is great !"
        
            var y: Int = 42
            def toString = s"$x / $y / ${z.getOrElse("not here !")}"
        }
        
        
        case class Toto(a: String, b: Double = 0D) 
        
        
        object Singleton {
            def g = "Le 'compilo' Scala assure que je suis un singleton"
            def get = new ScalaIsAwesome(Option("yo !"))
            val sayHelloFunc: String => String = x => x + " !"
        }
        
        
        trait MyTrait {
            def f = Toto("As trait I can have implementation")
        }Scala s'appui sur ce paradigme pour proposer une API "fonctionnelle"
    
        () => A <=> Function0[+A]
    
    
        A => B  <=> Function1[-A,+B]
    
        ...Programmation fonctionelle
Ce qu'on entend à propos de FP
- adapté à des tâches mathématiques & scientifiques
 - adapté à des algos complexe
 - vraiment bien pour la programmation parallèle
 - Mais on a besoin d'un doctorat pour comprendre
 - Pas vraiment adapté aux applis de gestion
 
Oh que si !
Functions as first-class citizen
object Main {
    // Une fonction est une valeur comme une autre
    val f: Int => String = x => x.toString
    // Elle peut donc être passée en paramètre d'une autre fonction
    def func1(f: Int => String, y: Int): String = f(y)
    // Et être retournée par une fonction
    def func2(devise: String): Double => String = x => x.toString + " " + devise
    // Ou les deux
    def func3(f: Int => String, y: Int): String => String = f(y) + " joueurs"
}Pures fonctions !
Conséquences
- Immutabilité
 - Transparence référentielle
 
Prendre du recul
Le boulot du programmeur
object WhatIsMyJob {
    def decompose(pb: ComplexProblem): List[LessComplexProblem]
    def findSolution(pb: LessComplexProblem): Solution
    def compose(actions: List[LessComplexProblem => Solution]): Application
    
    def main(complexProblem: ComplexProblem) {
        decompose(complexProblem) map { findSolution } andThen compose
    }
}Parlons un peu des types
Strong type vs weak type
Static type vs Dynamic type
The good choice !
______________________
MaClasse
val param1: String
val param2: Double
...
-------------------------------
def func1: String = ...
...
case class MyData(param1: String, 
                  param2: Double, 
                  ...)
object DomainFunc {
    def func1: String = ...
    ...
}Objet vs Fonctionnel
Les données d'un côté
Les fonctions de l'autre
Comment représenter les données ?
Comment composer les fonctions entre-elles ?
Un fil conducteur
    case class Contact(
        
        firstName: String,
        middleInitial: String,
        lastName: String,
        
        
        emailAddress: String,
        emailIsVerified: Boolean
    )Qu'est-ce qui ne va pas avec cette conception ?
valeur optionnelle ?
des contraintes ?
des champs liés ?
une logique métier ?
doit être annulé si email change ?
Types algébriques de données
data ProductType = AField * AnotherField * ...
data SumType = AType | AnotherType | ...
data AlgebraicDataType = AField * AnotherField |
                         MyField |
                         Constant 
                         ...Product & Sum Types
    data UnoCard = NumericCard { value: NumericValue, color: Color } |
                   KickBackCard { color: Color } |
                   PlusTwoCard { color: Color } |
                   ChangeColorCard |
                   PlusFourCard
    
    data NumericValue = Zero | One | Two | Three | Four | Five | 
                        Six | Seven | Eight | Nine
    
    data Color = Red | Blue | Green | YellowExemple plus parlant
    sealed trait UnoCard
    
    case class NumericCard(value: NumericValue, color: Color) extends UnoCard
    case class KickBackCard(color: Color) extends UnoCard
    case class PlusTwoCard(color: Color) extends UnoCard
    case object ChangeColorCard extends UnoCard
    case object PlusFourCard extends UnoCard
    
    sealed trait NumericValue
    case object Zero extends NumericValue
    case object One extends NumericValue
    case object Two extends NumericValue
    case object Three extends NumericValue
    case object Four extends NumericValue
    case object Five extends NumericValue
    case object Six extends NumericValue
    case object Seven extends NumericValue
    case object Eight extends NumericValue
    case object Nine extends NumericValue
    
    sealed trait Color
    case object Red extends Color
    case object Blue extends Color
    case object Green extends Color
    case object Yellow extends ColorEn Scala
Pattern matching
    def color(card: UnoCard): Option[Color] = ???
    def color(card: UnoCard): Option[Color] = card match {
        case NumericCard(_, color) => Some(color)
        case KickBackCard(color)   => Some(color)
        case PlusTwoCard(color)    => Some(color)
        case ChangeColorCard       => None
        case PlusFourCard          => None
    }Comment faire pour atteindre le champ "color" ?
Surtout que toutes les cartes n'ont pas de couleur
Utiliser le pattern matching
    case class Contact(
        
        firstName: String,
        middleInitial: String,
        lastName: String,
        
        
        emailAddress: String,
        emailIsVerified: Boolean
    )Qu'est-ce qui ne va pas avec cette conception ?
valeur optionnelle ?
des contraintes ?
des champs liés ?
une logique métier ?
doit être annulé si email change ?
    case class PersonalName(
        firstName: String_50, 
        middleInitial: Option[String_1], 
        lastName: String_50)
    sealed trait EmailAddress
    case class VerifiedEmail(email: Email) extends EmailAddress
    case class UnverifiedEmail(email: Email) extends EmailAddress
    case class Contact(
        name: PersonalName,
        emailAddress: EmailAddress
    )
    object String_50 {
        def apply(rawValue: String): NotValidValue \/ String_50 = ???
    }Avec les Types Algébriques de Données
Représenter l'absence de valeur
    
    sealed trait Option[A]
    
    case class Some[A](x: A) extends Option[A]
    case object None extends Option[Nothing]    
    def filterValueMinus10(i: Int): Option[Int] = 
        if(i < 10) Some(i)
        else NoneReprésenter une erreur
    
    sealed trait \/[+A, +B]
    
    case class -\/[+A](a: A) extends (A \/ Nothing)
    case class \/-[+B](b: B) extends (Nothing \/ B)    
    def forbidValueMinus10(i: Int): IllegalArgumentException \/ Int = 
        if(i < 10) new IllegalArgumentException(s"value is minus than 10 [$i]").left
        else i.rightReprésenter un IO
    
    def println(x: String): IO[Unit] = ??? // On verra après pour plus de détailThéorie des catégories
C'est des maths !
La théorie des catégories étudie les structures mathématiques et les relations qu'elles entretiennent.
Catégorie ...
- un ensemble E,
 - une loi de composition LC
 - des règles sur E et LC
 
En gros
Pourquoi tu nous saoules avec des maths ?
Bon donc une fois que j'aurais passé ma thèse je pourrais commencer la programmation fonctionnelle ?

Les catégories comme "Pattern" pour résoudre des problèmes de conception
    
    def f: Int => List[String]
    def g: String => StringComment composer f et g
    val result: List[String] = f(2).map { s => g(s) }
    val result: List[String] = f(2) map(g)C'est un Foncteur !
Composer une valeur et un context avec la fonction map
    trait Functor[F[_]] {
        def map[A,B](f: A => B)(fa: F[A]): F[B]
    }
    // Tous les Functor doivent suivrent 2 lois
    map (id) = id
    map (f compose g) = map f compose map g    
    trait Exemple[A] {
        def f: () => Option[A]
        def g[B]: A => Option[B]
    }Comment composer f et g
    val result: Option[B] = f().flatMap { a => g(a) }
    val result: Option[B] = f() flatMap(g)C'est une Monade !
Composer une valeur et une succession de contexte avec la fonction flatMap
    trait Monad[M[_]] {
        def point[A](a: => A): M[A]
        def flatMap[A,B](ma: M[A])(f: A => M[B]): M[B]
    }
    // Toutes les Monades doivent suivre 3 lois
    avec a: A, ma: M[A], f: A => m[B] et g : B => M[C]; 
    flatMap(ma)(point)          <=> ma                                  // right identity
    
    flatMap(point(a))(f)        <=> f(a)                                // left identity
    flatMap( flatMap(ma)(f))(g) <=> flatMap(ma)(x => flatMap(f(x))(g))  // associativityMais aussi
- Monoïde
 - Applicative
 - Kleisli
 - ...
 
Pure IO
Concept
    
    trait IO[A] {
        def effect(x: => A): IO[A]
        def unsafePerformIO(io: IO[A]): A
    }   Utiliser le concept de lazy evaluation pour différer l'application de l'effet de bord.
Décrire l'effet de bord par un type.
Appliquer les effets à la fin du programme.
Concept
Décrire ce que le programme doit faire
Déclencher l'éxécution de ce programme à un autre moment (à la fin du programme)
    
        println("vive")
        println("la ")
        println("programmation fonctionnelle !")
    def println(x: String): IO[Unit]    
    println("vive")
        .flatMap { _ => println("la ") }
        .flatMap { _ => println("programmation fonctionnelle !") }Pour utiliser le type IO, il nous faut une Monade !
Exemple
    def println(x: String): IO[Unit]    
    object Main {
    
        def main(id: String): Unit = {
            val result = for {
                _    <- println("démarre le programme")
                user <- findUser(id)
                _    <- println(s"l'utilisateur se nomme ${user.name}"
            } yield ()
            result.unsafePerformIO()
        }
    }Exemple - for comprehension
Chaque ADT peut bénéficier de ces abstractions
- Option
 - List
 - \/ (a.k.a Either a.k.a disjonction)
 - Task (async IO)
 - Process (non-blocking async streaming)
 - ...
 
Notre programme est constitué d'un ensemble de type qui décrivent des effets.
Il faut pouvoir les composer entre eux.
Les catégories (monades, foncteurs, ...) sont des abstractions utiles qui nous permettent de faire cela avec élégances.
Nous nous retrouvons alors à décrire l’exécution d'un programme, comme une suite de composition de fonction pure.
Par définition, ce programme est composable avec un autre programme de même type.
Nous pouvons donc raisonnablement (avec raison) recomposer notre décomposition qui nous permet de résoudre notre problème métier.
Paramétricité
Sommer des entiers
    def sum(xs: List[Int]): Int = xs.foldLeft(0)(_+_)    // notation spéciale dédicace ^^Et pour des Double ?
    def sum(xs: List[Double]): Double = xs.foldLeft(0D) { (x,y) => x + y }Les String ?
    def sum(xs: List[String]): String = xs.foldLeft("") { _ + _ }    
    // Même les listes !!!
    def sum[A](xs: List[List[A]]): List[A] = xs.foldLeft(Nil) { _ ++ _ }Comment supprimer cette duplication ?
    def sum[A](xs: List[A]): A = xs.foldLeft(0)(_+_) Utiliser un type paramétré (générique)
mais A n'a pas de méthode (+) ni de zéro
Classes de type
a.k.a Typeclass
Typeclass
    def sum[A](xs: List[A])(implicit m: Monoid[A]): A = xs.foldLeft(m.zero)(m.plus) 
    trait Monoid[M] {
	def zero: M
	def plus(x: M, y: M): M
    }Et sinon concrètement ?
    def sum[A](xs: List[A])(implicit m: Monoid[A]): A = xs.foldLeft(m.zero)(m.plus) 
    trait Monoid[M] {
	def zero: M
	def plus(x: M, y: M): M
    }
    import Monoid.IntMonoid
    
    val ints = 1 :: 2 :: 3 :: 4 :: Nil
    sum(ints) shouldBe 10 
    object Monoid {
	implicit object IntMonoid extends Monoid[Int] {
            def zero: Int = 0
            def plus(x: Int, y: Int) = x + y
        }
    }Double, String, List, Price, ...
Free Theorem
    // Cette fonction multiplie par deux
    def f(x: Int): Int = ???Que fait f ?
    
    // Cette fonction multiplie par deux
    def f(x: Int): Int = x + 9Les commentaires ce n'est pas fiable
    
    def addNine(x: Int): Int = ???mieux vaut nommer ses fonctions
    
    def addNine(x: Int): Int = x * 2Ah bas en fait non :)
Et là que fait f ?
    
    def f[A](x: A): A = ???Une seule implémentation possible
    
    def f[A](x: A): A = xLes types ne mentent pas
On en déduit
- Les commentaires mentent
 - Les noms de fonctions mentent
 - Seul les Types ne mentent pas
 
Les types de mentent pas
    
    def f[A](x: A): A = xMais il faut généraliser pour pouvoir restreindre les choix d'implémentation
Un autre exemple
    def compose[A,B,C](g: B => C, f: A => B): A => C = (x: A) => g(f(x))
    def compose[A,B,C](g: B => C, f: A => B): A => C = g compose f
    def compose[A,B,C](g: B => C, f: A => B): A => C = f andThen g 
Un contre-exemple ?
    def reverse[A](xs: List[A]): List[A]Que peut on dire ?
Tous les éléments retournés par reverse se trouvent dans la liste d'entrée
Property based testing
    def reverse[A](xs: List[A]): List[A]
    ∀x. reverse(reverse(x)) == x
    ∀x. ∀y. reverse(x ++ y) == reverse(y) ++  reverse(x)cf scalacheck, quickcheck, etc...
Paramétricité
- Les types sont la seule documentation fiable
 - S'ils ne suffisent pas, les tests de propriétés sont là
 - Paramétricité permet d'enlever bcp de duplication (grâce aux classes de type)
 - OCP (Open Close Principle) est implémenté avec facilité & élégance => code robuste
 
Conclusion
- Facile à tester
 - Programmation "sure"
 - Abstractions puissantes
 - Un programme explicite | descriptif
 - Documentation métier riche
 - Élimination de la duplication facilitée
 
Conclusion
- Choisir un langage "pur"
 - ou s'astreindre à une discipline rigoureuse
 - Montée en complexité initiale par rapport à la programmation impérative
 - mais plus simple ensuite (= moins compliqué)
 - Paradigme qui progresse dans la communauté des dev mais pas encore très répandu
 
FP everywhere ?
Appliquer les concepts dans d'autres contextes
Du code fonctionnel partout
- Back (Scala, F#, Haskell, ...)
 - Front (javascript?, purescript, elm, ghc.js, ...)
 
Immutable/functional Database
Immutable/functional Infrastructure
FP in Scala
By ugobourdon
FP in Scala
Introduction to functional programming with example in Scala
- 2,255