SlideShare ist ein Scribd-Unternehmen logo
1 von 37
Downloaden Sie, um offline zu lesen
ANATOMIE D'UNEANATOMIE D'UNE TYPE CLASSTYPE CLASS
SOMMAIRESOMMAIRE
Moi, moi, moi !
Relation donnée / comportement: divergence de points de vue
Polymorquoi ?
Anatomie de la type class
Type class meca-augmentée !
La boite à outils
Aller plus haut !
MOI, MOI, MOI !MOI, MOI, MOI !
Data ingénieur Ebiznext
ESN avec une forte expertise autour de la data / Scala
Animateur du pôle data
En charge de la branche nantaise
Co-organisateur du SNUG
Passionné de FP
@mmenestret
geekocephale.com
RELATION DONNÉE / COMPORTEMENT:RELATION DONNÉE / COMPORTEMENT:
DIVERGENCE DE POINTS DE VUE !DIVERGENCE DE POINTS DE VUE !
SÉPARATION DONNÉE / COMPORTEMENTSÉPARATION DONNÉE / COMPORTEMENT
La programmation orientée objet et la programmation fonctionnelle: deux approches opposées !
ORIENTÉ OBJETORIENTÉ OBJET
L'OOP combine la donnée et les comportements au sein de classes
Encapsule et cache la donnée dans un état interne
Expose les comportements sous forme de méthodes pour agir sur celui-ci
final class Player(private val name: String, private var level: Int) {
def levelUp(): Unit = { level = level + 1 }
def sayHi(): String = s"Hi, I'm player $name, I'm lvl $level !"
}
PROGRAMMATION FONCTIONNELLEPROGRAMMATION FONCTIONNELLE
La FP sépare complètement la donnée des comportements
La donnée est modelisée par des types algébriques de donnée (ADTs)
Les comportement sont modélisés par des fonctions (depuis et vers ces types)
final case class Player(name: String, level: Int)
object PlayerOperations {
def levelUp(p: Player): Player = p.copy(level = p.level + 1)
def sayHi(p: Player): String = s"Hi, I'm player ${p.name}, I'm lvl ${p.level} !"
}
EXPRESSION PROBLEMEXPRESSION PROBLEM
Comment se comporte un langage ou un paradigme quand on:
Étend un type existant (ajouter des "cas" à un type)
Personnage = Joueur + Personnage non joueur
Et si on ajoute Boss ?
Étend les comportements d'un type existant
Un personnage peut dire bonjour et monter en niveau
Et si on ajoute le comportement de se déplacer ?
Ces actions entrainent-elles des modifications de la code base existante ?
ORIENTÉ OBJETORIENTÉ OBJET
: Étendre un type existant
Juste une nouvelle classe qui extends mon type à étendre (la code base d'origine reste inchangée)
: Étendre les comportements d'un type existant
Nouvelle méthode sur le type dont on veut étendre le comportement
Impact sur tous ses sous types pour y implémenter cette nouvelle méthode...
PROGRAMMATION FONCTIONNELLEPROGRAMMATION FONCTIONNELLE
: Étendre un type existant
Nouvelle implémentation du trait représentant le type à étendre
Impact sur toutes les fonctions existantes prenant ce type en paramètre pour traiter cette nouvelle
implémentation...
: Étendre les comportements d'un type existant
Juste une nouvelle fonction (la code base d'origine reste inchangée)
POLYMORQUOI ?POLYMORQUOI ?
DEFINITIONDEFINITION
Mécanisme visant à augmenter la réutilisation de code grâce à des constructions plus génériques.
Il y a plusieurs types de polymorphisme.
POLYMORPHISME PARAMÉTRIQUEPOLYMORPHISME PARAMÉTRIQUE
Une fonction se réfère à un symbole abstrait qui peut représenter n'importe quel type.
def reverse[A](as: List[A]): List[A] = ???
POLYMORPHISME D'HÉRITAGEPOLYMORPHISME D'HÉRITAGE
Plusieurs classes héritent leurs comportements d'une super classe commune.
class Character(private val name: String) {
def sayHi(): String = s"Hi, I'm $name"
}
class Player(private val name: String, private var level: Int) extends Character(name) {
def levelUp(): Unit = { level = level + 1 }
}
POLYMORPHISME AD HOCPOLYMORPHISME AD HOC
Une fonction se réfère à une "interface" commune à un ensemble de types arbitraires.
Cette "interface" abstrait un ou plusieurs comportements communs à ces types
Evite de ré-implémenter une fonction pour chaque type concrets
Son comportement dépendra du type concret de son / ses paramètre(s)
On va s'intéresser à celui-ci !
DEUX IMPLÉMENTATIONS DU POLYMORPHISME AD HOCDEUX IMPLÉMENTATIONS DU POLYMORPHISME AD HOC
Interface subtyping / adapter pattern - ಥ_ಥ
def show(s: Showable): String
Type classes - ᕕ( ᐛ )ᕗ
def show[S: Showable](s: S): String
ANATOMIE DE LAANATOMIE DE LA TYPE CLASSTYPE CLASS
OBSERVATIONSOBSERVATIONS
Construction introduite en Haskell par Philip Wadler
Représente un groupe ou une "classe" de types (type class) arbitraire qui partagent des propriétés
communes
Par exemple:
Le groupe de ceux qui peuvent dire "bonjour"
Le groupe de ceux qui ont des pétales
ANATOMIE COMPARÉEANATOMIE COMPARÉE
Joue le même rôle qu'une interface en OOP, MAIS:
Permet d'ajouter des propriétés à des types existant à posteriori
Permet d'encoder une interface conditionnelle
ANATOMIE FONCTIONNELLEANATOMIE FONCTIONNELLE
En Scala, on encode les type classes, ce n'est pas une construction de première classe du langage mais un
design pattern (ce n'est pas le cas de tous les langages...).
On l'implémente grâce à:
1. Un trait avec un paramètre de type qui expose les propriétés qui sont abstraites par la type class
2. Les implémentations concrètes de ce trait
ETUDE D'UN SPÉCIMENETUDE D'UN SPÉCIMEN
// Notre classe "métier"
final case class Player(name: String, level: Int)
val geekocephale = Player("Geekocephale", 42)
// 1. Un trait: tous les T qui peuvent dire bonjour
trait CanSayHi[T] {
def sayHi(t: T): String
}
// 2. Une implémentation concrète pour que Player soit une instance de CanSayHi
val playerGreeter: CanSayHi[Player] = new CanSayHi[Player] {
def sayHi(t: Player): String = s"Hi, I'm player ${t.name}, I'm lvl ${t.level} !"
}
// Une fonction polymorphique
def greet[T](t: T, greeter: CanSayHi[T]): String = greeter.sayHi(t)
scala> greet(geekocephale, playerGreeter)
res4: String = Hi, I'm player Geekocephale, I'm lvl 42 !
ANATOMIE FONCTIONNELLEANATOMIE FONCTIONNELLE
Utilisons les implicits pour se rapprocher de ce qui est fait en Haskell
def greet[T](t: T)(implicit greeter: CanSayHi[T]): String = greeter.sayHi(t)
implicit val playerGreeter: CanSayHi[Player] = new CanSayHi[Player] {
def sayHi(t: Player): String = s"Hi, I'm player ${t.name}, I'm lvl ${t.level} !"
}
scala> greet(geekocephale)
res5: String = Hi, I'm player Geekocephale, I'm lvl 42 !
NOTA BENENOTA BENE
2 règles d'hygiène fondamentales:
Une seule implémentation d'une type class par type
On ne met les instances de type class que:
Dans l'object compagnon de la type class
Dans l'object compagnon du type
RETOUR À L'ANATOMIE COMPARÉERETOUR À L'ANATOMIE COMPARÉE
AJOUT DE PROPRIÉTÉS À DES TYPES EXISTANTSAJOUT DE PROPRIÉTÉS À DES TYPES EXISTANTS
Maintenant votre URL sait dire bonjour !
import java.net.URL
implicit val urlGreeter: CanSayHi[URL] = new CanSayHi[URL] {
override def sayHi(t: URL): String = s"Hi, I'm an URL pointing at ${t.getHost}"
}
scala> greet(new URL("http://geekocephale.com"))
res6: String = Hi, I'm an URL pointing at geekocephale.com
RETOUR À L'ANATOMIE COMPARÉERETOUR À L'ANATOMIE COMPARÉE
INTERFAÇAGE CONDITIONNELINTERFAÇAGE CONDITIONNEL
A est une instance de la type class CanSayHi si et seulement si A est également une instance de
CanSayItsName.
trait CanSayItsName[A] {
def sayMyName(a: A): String
}
implicit def greeter[A](implicit nameSayer: CanSayItsName[A]): CanSayHi[A] = new CanSayHi[A] {
override def sayHi(a: A): String = s"Hi, I'm ${nameSayer.sayMyName(a)} !"
}
RETOUR À L'ANATOMIE COMPARÉERETOUR À L'ANATOMIE COMPARÉE
INTERFAÇAGE CONDITIONNELINTERFAÇAGE CONDITIONNEL
Guild est une instance de la type class CanSayHi si et seulement si Player en est une instance également.
final case class Guild(members: List[Player])
implicit def guildGreeter(implicit playerGreeter: CanSayHi[Player]): CanSayHi[Guild] = new CanSayHi[Guild] {
override def sayHi(g: Guild): String = s"""Hi, we are ${g.members.map(p => playerGreeter.sayHi(p).mkString(","))}"""
}
TYPE CLASSTYPE CLASS MECA-AUGMENTÉE !MECA-AUGMENTÉE !
CONTEXT BOUNDCONTEXT BOUND
def greet[T](t: T)(implicit greeter: CanSayHi[T]): String = ???
Peut être refactoré en (absolument identique):
def greet[T: CanSayHi](t: T): String = ???
Plus clean et exprime plus clairement la contrainte que T doit être une instance de CanSayHi
TYPE CLASSTYPE CLASS APPLYAPPLY
Mais comment récupère t-on notre greeter ?
... implicitly[CanSayHi[T]]...
C'est mieux !
def greet[T: CanSayHi](t: T): String = {
val greeter: CanSayHi[T] = implicitly[CanSayHi[T]]
greeter.sayHi(t)
}
object CanSayHi {
def apply[T](implicit C: CanSayHi[T]): CanSayHi[T] = C
}
def greet[T: CanSayHi](t: T): String = CanSayHi[T].sayHi(t)
TYPE CLASSTYPE CLASS SYNTAXSYNTAX
On peut utiliser les implicit class pour ajouter la syntax de notre type class
Ce qui nous permet d'écrire: geekocephale.greet
C'est important une bonne syntaxe !
implicit class CanSayHiSyntax[T: CanSayHi](t: T) {
def greet: String = CanSayHi[T].sayHi(t)
}
TOUS ENSEMBLE !TOUS ENSEMBLE !
trait CanSayHi[T] {
def sayHi(t: T): String
}
object CanSayHi {
def apply[T](implicit C: CanSayHi[T]): CanSayHi[T] = C
}
implicit class CanSayHiSyntax[T: CanSayHi](t: T) {
def greet: String = CanSayHi[T].sayHi(t)
}
final case class Player(name: String, var level: Int)
object Player {
implicit val playerGreeter: CanSayHi[Player] = new CanSayHi[Player] {
def sayHi(t: Player): String = s"Hi, I'm player ${t.name}, I'm lvl ${t.level} !"
}
}
LA BOITE À OUTILSLA BOITE À OUTILS
SIMULACRUMSIMULACRUM
permet de se débarasser du boiler plate en le générant automatiquement, à la compilation,
grâce à des macros
Simulacrum
import simulacrum._
@typeclass trait CanSayHi[T] {
@op("greet") def sayHi(t: T): String
}
MAGNOLIAMAGNOLIA
permet la dérivation automatique de type classes pour les ADTs
Product types:
Sum types:
Si A et B sont des instances d'une type class T, alors C l'est aussi, "automatiquement" !
Magnolia
type A
type B
final case class C(a: A, b: B)
sealed trait C
final case class A() extends C
final case class B() extends C
ALLER PLUS HAUT !ALLER PLUS HAUT !
FP resources list
Anatomy of a type class
Inheritance vs Generics vs TypeClasses in Scala
Mastering Typeclass Induction
Type class, ultimate ad hoc
Type classes in Scala
Implicits, type classes, and extension methods
CONCLUSIONCONCLUSION
Les type classes permettent:
De ne pas mixer comportements et donnée
L'ad hoc polymorphism
D'ajouter du comportement à un type à posteriori
MERCI !MERCI !
@MMENESTRET@MMENESTRET
GEEKOCEPHALE.COMGEEKOCEPHALE.COM

Weitere ähnliche Inhalte

Was ist angesagt?

Mémento caml
Mémento camlMémento caml
Mémento caml
zan
 

Was ist angesagt? (19)

Chapitre 2: String en Java
Chapitre 2:  String en JavaChapitre 2:  String en Java
Chapitre 2: String en Java
 
Ch13
Ch13Ch13
Ch13
 
Héritage et polymorphisme- Jihen HEDHLI
Héritage et polymorphisme- Jihen HEDHLIHéritage et polymorphisme- Jihen HEDHLI
Héritage et polymorphisme- Jihen HEDHLI
 
Mémento caml
Mémento camlMémento caml
Mémento caml
 
Les expressions régulières en java
Les expressions régulières en javaLes expressions régulières en java
Les expressions régulières en java
 
La programmation modulaire en Python
La programmation modulaire en PythonLa programmation modulaire en Python
La programmation modulaire en Python
 
Introduction à scala
Introduction à scalaIntroduction à scala
Introduction à scala
 
Partie 2: Types, Variables, Opérateurs — Programmation orientée objet en C++
Partie 2: Types, Variables, Opérateurs — Programmation orientée objet en C++Partie 2: Types, Variables, Opérateurs — Programmation orientée objet en C++
Partie 2: Types, Variables, Opérateurs — Programmation orientée objet en C++
 
Chapitre1: Langage Python
Chapitre1: Langage PythonChapitre1: Langage Python
Chapitre1: Langage Python
 
Polymorphisme : un concept polymorphe !
Polymorphisme : un concept polymorphe !Polymorphisme : un concept polymorphe !
Polymorphisme : un concept polymorphe !
 
JAVA
JAVAJAVA
JAVA
 
chapitre1.ppt
chapitre1.pptchapitre1.ppt
chapitre1.ppt
 
De java à swift en 2 temps trois mouvements
De java à swift en 2 temps trois mouvementsDe java à swift en 2 temps trois mouvements
De java à swift en 2 temps trois mouvements
 
Chapitre8: Collections et Enumerations En Java
Chapitre8: Collections et Enumerations En JavaChapitre8: Collections et Enumerations En Java
Chapitre8: Collections et Enumerations En Java
 
Librairies Java qui changent la vie
Librairies Java qui changent la vieLibrairies Java qui changent la vie
Librairies Java qui changent la vie
 
Améliorations dans Java depuis la version 5
Améliorations dans Java depuis la version 5Améliorations dans Java depuis la version 5
Améliorations dans Java depuis la version 5
 
Mort au boilerplate avec scala meta
Mort au boilerplate avec scala metaMort au boilerplate avec scala meta
Mort au boilerplate avec scala meta
 
Bases de php - Partie 4
Bases de php - Partie 4Bases de php - Partie 4
Bases de php - Partie 4
 
Les fondamentaux du langage C
Les fondamentaux du langage CLes fondamentaux du langage C
Les fondamentaux du langage C
 

Ähnlich wie Anatomie d'une typeclass

Javascript un langage supérieur
Javascript un langage supérieurJavascript un langage supérieur
Javascript un langage supérieur
Fredy Fadel
 
INF120 - Algo DUT SRC1 - Cours 3
INF120 - Algo DUT SRC1 - Cours 3INF120 - Algo DUT SRC1 - Cours 3
INF120 - Algo DUT SRC1 - Cours 3
PGambette
 
Introduction au langage Ruby
Introduction au langage RubyIntroduction au langage Ruby
Introduction au langage Ruby
Julien Blin
 

Ähnlich wie Anatomie d'une typeclass (20)

Ruby Pour RoR
Ruby Pour RoRRuby Pour RoR
Ruby Pour RoR
 
Développement Web- PHP (partie I).pdf
Développement Web- PHP (partie I).pdfDéveloppement Web- PHP (partie I).pdf
Développement Web- PHP (partie I).pdf
 
Mix it 2011 - Clojure
Mix it 2011 - ClojureMix it 2011 - Clojure
Mix it 2011 - Clojure
 
Introduction à Python - Achraf Kacimi El Hassani
Introduction à Python - Achraf Kacimi El HassaniIntroduction à Python - Achraf Kacimi El Hassani
Introduction à Python - Achraf Kacimi El Hassani
 
Formation python micro club.net
Formation python micro club.netFormation python micro club.net
Formation python micro club.net
 
Javascript un langage supérieur
Javascript un langage supérieurJavascript un langage supérieur
Javascript un langage supérieur
 
Java
JavaJava
Java
 
INF120 - Algo DUT SRC1 - Cours 3
INF120 - Algo DUT SRC1 - Cours 3INF120 - Algo DUT SRC1 - Cours 3
INF120 - Algo DUT SRC1 - Cours 3
 
Patrons de conception de la programmation fonctionnelle
Patrons de conception de la programmation fonctionnellePatrons de conception de la programmation fonctionnelle
Patrons de conception de la programmation fonctionnelle
 
Généralités sur la notion d’Algorithme
Généralités sur la notion d’AlgorithmeGénéralités sur la notion d’Algorithme
Généralités sur la notion d’Algorithme
 
Design poo togo_jug_final
Design poo togo_jug_finalDesign poo togo_jug_final
Design poo togo_jug_final
 
Design poo togo_jug_final
Design poo togo_jug_finalDesign poo togo_jug_final
Design poo togo_jug_final
 
Introduction au langage Ruby
Introduction au langage RubyIntroduction au langage Ruby
Introduction au langage Ruby
 
cours1.ppt
cours1.pptcours1.ppt
cours1.ppt
 
cours1.ppt
cours1.pptcours1.ppt
cours1.ppt
 
cours2.ppt
cours2.pptcours2.ppt
cours2.ppt
 
.php1 : les fondamentaux du PHP
.php1 : les fondamentaux du PHP.php1 : les fondamentaux du PHP
.php1 : les fondamentaux du PHP
 
Drools
DroolsDrools
Drools
 
Php4 Mysql
Php4 MysqlPhp4 Mysql
Php4 Mysql
 
Initiation au JavaScript
Initiation au JavaScriptInitiation au JavaScript
Initiation au JavaScript
 

Anatomie d'une typeclass

  • 1. ANATOMIE D'UNEANATOMIE D'UNE TYPE CLASSTYPE CLASS
  • 2. SOMMAIRESOMMAIRE Moi, moi, moi ! Relation donnée / comportement: divergence de points de vue Polymorquoi ? Anatomie de la type class Type class meca-augmentée ! La boite à outils Aller plus haut !
  • 3. MOI, MOI, MOI !MOI, MOI, MOI ! Data ingénieur Ebiznext ESN avec une forte expertise autour de la data / Scala Animateur du pôle data En charge de la branche nantaise Co-organisateur du SNUG Passionné de FP @mmenestret geekocephale.com
  • 4. RELATION DONNÉE / COMPORTEMENT:RELATION DONNÉE / COMPORTEMENT: DIVERGENCE DE POINTS DE VUE !DIVERGENCE DE POINTS DE VUE !
  • 5. SÉPARATION DONNÉE / COMPORTEMENTSÉPARATION DONNÉE / COMPORTEMENT La programmation orientée objet et la programmation fonctionnelle: deux approches opposées !
  • 6. ORIENTÉ OBJETORIENTÉ OBJET L'OOP combine la donnée et les comportements au sein de classes Encapsule et cache la donnée dans un état interne Expose les comportements sous forme de méthodes pour agir sur celui-ci final class Player(private val name: String, private var level: Int) { def levelUp(): Unit = { level = level + 1 } def sayHi(): String = s"Hi, I'm player $name, I'm lvl $level !" }
  • 7. PROGRAMMATION FONCTIONNELLEPROGRAMMATION FONCTIONNELLE La FP sépare complètement la donnée des comportements La donnée est modelisée par des types algébriques de donnée (ADTs) Les comportement sont modélisés par des fonctions (depuis et vers ces types) final case class Player(name: String, level: Int) object PlayerOperations { def levelUp(p: Player): Player = p.copy(level = p.level + 1) def sayHi(p: Player): String = s"Hi, I'm player ${p.name}, I'm lvl ${p.level} !" }
  • 8. EXPRESSION PROBLEMEXPRESSION PROBLEM Comment se comporte un langage ou un paradigme quand on: Étend un type existant (ajouter des "cas" à un type) Personnage = Joueur + Personnage non joueur Et si on ajoute Boss ? Étend les comportements d'un type existant Un personnage peut dire bonjour et monter en niveau Et si on ajoute le comportement de se déplacer ? Ces actions entrainent-elles des modifications de la code base existante ?
  • 9. ORIENTÉ OBJETORIENTÉ OBJET : Étendre un type existant Juste une nouvelle classe qui extends mon type à étendre (la code base d'origine reste inchangée) : Étendre les comportements d'un type existant Nouvelle méthode sur le type dont on veut étendre le comportement Impact sur tous ses sous types pour y implémenter cette nouvelle méthode...
  • 10. PROGRAMMATION FONCTIONNELLEPROGRAMMATION FONCTIONNELLE : Étendre un type existant Nouvelle implémentation du trait représentant le type à étendre Impact sur toutes les fonctions existantes prenant ce type en paramètre pour traiter cette nouvelle implémentation... : Étendre les comportements d'un type existant Juste une nouvelle fonction (la code base d'origine reste inchangée)
  • 12. DEFINITIONDEFINITION Mécanisme visant à augmenter la réutilisation de code grâce à des constructions plus génériques. Il y a plusieurs types de polymorphisme.
  • 13. POLYMORPHISME PARAMÉTRIQUEPOLYMORPHISME PARAMÉTRIQUE Une fonction se réfère à un symbole abstrait qui peut représenter n'importe quel type. def reverse[A](as: List[A]): List[A] = ???
  • 14. POLYMORPHISME D'HÉRITAGEPOLYMORPHISME D'HÉRITAGE Plusieurs classes héritent leurs comportements d'une super classe commune. class Character(private val name: String) { def sayHi(): String = s"Hi, I'm $name" } class Player(private val name: String, private var level: Int) extends Character(name) { def levelUp(): Unit = { level = level + 1 } }
  • 15. POLYMORPHISME AD HOCPOLYMORPHISME AD HOC Une fonction se réfère à une "interface" commune à un ensemble de types arbitraires. Cette "interface" abstrait un ou plusieurs comportements communs à ces types Evite de ré-implémenter une fonction pour chaque type concrets Son comportement dépendra du type concret de son / ses paramètre(s) On va s'intéresser à celui-ci !
  • 16. DEUX IMPLÉMENTATIONS DU POLYMORPHISME AD HOCDEUX IMPLÉMENTATIONS DU POLYMORPHISME AD HOC Interface subtyping / adapter pattern - ಥ_ಥ def show(s: Showable): String Type classes - ᕕ( ᐛ )ᕗ def show[S: Showable](s: S): String
  • 17. ANATOMIE DE LAANATOMIE DE LA TYPE CLASSTYPE CLASS
  • 18. OBSERVATIONSOBSERVATIONS Construction introduite en Haskell par Philip Wadler Représente un groupe ou une "classe" de types (type class) arbitraire qui partagent des propriétés communes Par exemple: Le groupe de ceux qui peuvent dire "bonjour" Le groupe de ceux qui ont des pétales
  • 19. ANATOMIE COMPARÉEANATOMIE COMPARÉE Joue le même rôle qu'une interface en OOP, MAIS: Permet d'ajouter des propriétés à des types existant à posteriori Permet d'encoder une interface conditionnelle
  • 20. ANATOMIE FONCTIONNELLEANATOMIE FONCTIONNELLE En Scala, on encode les type classes, ce n'est pas une construction de première classe du langage mais un design pattern (ce n'est pas le cas de tous les langages...). On l'implémente grâce à: 1. Un trait avec un paramètre de type qui expose les propriétés qui sont abstraites par la type class 2. Les implémentations concrètes de ce trait
  • 21. ETUDE D'UN SPÉCIMENETUDE D'UN SPÉCIMEN // Notre classe "métier" final case class Player(name: String, level: Int) val geekocephale = Player("Geekocephale", 42) // 1. Un trait: tous les T qui peuvent dire bonjour trait CanSayHi[T] { def sayHi(t: T): String } // 2. Une implémentation concrète pour que Player soit une instance de CanSayHi val playerGreeter: CanSayHi[Player] = new CanSayHi[Player] { def sayHi(t: Player): String = s"Hi, I'm player ${t.name}, I'm lvl ${t.level} !" } // Une fonction polymorphique def greet[T](t: T, greeter: CanSayHi[T]): String = greeter.sayHi(t) scala> greet(geekocephale, playerGreeter) res4: String = Hi, I'm player Geekocephale, I'm lvl 42 !
  • 22. ANATOMIE FONCTIONNELLEANATOMIE FONCTIONNELLE Utilisons les implicits pour se rapprocher de ce qui est fait en Haskell def greet[T](t: T)(implicit greeter: CanSayHi[T]): String = greeter.sayHi(t) implicit val playerGreeter: CanSayHi[Player] = new CanSayHi[Player] { def sayHi(t: Player): String = s"Hi, I'm player ${t.name}, I'm lvl ${t.level} !" } scala> greet(geekocephale) res5: String = Hi, I'm player Geekocephale, I'm lvl 42 !
  • 23. NOTA BENENOTA BENE 2 règles d'hygiène fondamentales: Une seule implémentation d'une type class par type On ne met les instances de type class que: Dans l'object compagnon de la type class Dans l'object compagnon du type
  • 24. RETOUR À L'ANATOMIE COMPARÉERETOUR À L'ANATOMIE COMPARÉE AJOUT DE PROPRIÉTÉS À DES TYPES EXISTANTSAJOUT DE PROPRIÉTÉS À DES TYPES EXISTANTS Maintenant votre URL sait dire bonjour ! import java.net.URL implicit val urlGreeter: CanSayHi[URL] = new CanSayHi[URL] { override def sayHi(t: URL): String = s"Hi, I'm an URL pointing at ${t.getHost}" } scala> greet(new URL("http://geekocephale.com")) res6: String = Hi, I'm an URL pointing at geekocephale.com
  • 25. RETOUR À L'ANATOMIE COMPARÉERETOUR À L'ANATOMIE COMPARÉE INTERFAÇAGE CONDITIONNELINTERFAÇAGE CONDITIONNEL A est une instance de la type class CanSayHi si et seulement si A est également une instance de CanSayItsName. trait CanSayItsName[A] { def sayMyName(a: A): String } implicit def greeter[A](implicit nameSayer: CanSayItsName[A]): CanSayHi[A] = new CanSayHi[A] { override def sayHi(a: A): String = s"Hi, I'm ${nameSayer.sayMyName(a)} !" }
  • 26. RETOUR À L'ANATOMIE COMPARÉERETOUR À L'ANATOMIE COMPARÉE INTERFAÇAGE CONDITIONNELINTERFAÇAGE CONDITIONNEL Guild est une instance de la type class CanSayHi si et seulement si Player en est une instance également. final case class Guild(members: List[Player]) implicit def guildGreeter(implicit playerGreeter: CanSayHi[Player]): CanSayHi[Guild] = new CanSayHi[Guild] { override def sayHi(g: Guild): String = s"""Hi, we are ${g.members.map(p => playerGreeter.sayHi(p).mkString(","))}""" }
  • 27. TYPE CLASSTYPE CLASS MECA-AUGMENTÉE !MECA-AUGMENTÉE !
  • 28. CONTEXT BOUNDCONTEXT BOUND def greet[T](t: T)(implicit greeter: CanSayHi[T]): String = ??? Peut être refactoré en (absolument identique): def greet[T: CanSayHi](t: T): String = ??? Plus clean et exprime plus clairement la contrainte que T doit être une instance de CanSayHi
  • 29. TYPE CLASSTYPE CLASS APPLYAPPLY Mais comment récupère t-on notre greeter ? ... implicitly[CanSayHi[T]]... C'est mieux ! def greet[T: CanSayHi](t: T): String = { val greeter: CanSayHi[T] = implicitly[CanSayHi[T]] greeter.sayHi(t) } object CanSayHi { def apply[T](implicit C: CanSayHi[T]): CanSayHi[T] = C } def greet[T: CanSayHi](t: T): String = CanSayHi[T].sayHi(t)
  • 30. TYPE CLASSTYPE CLASS SYNTAXSYNTAX On peut utiliser les implicit class pour ajouter la syntax de notre type class Ce qui nous permet d'écrire: geekocephale.greet C'est important une bonne syntaxe ! implicit class CanSayHiSyntax[T: CanSayHi](t: T) { def greet: String = CanSayHi[T].sayHi(t) }
  • 31. TOUS ENSEMBLE !TOUS ENSEMBLE ! trait CanSayHi[T] { def sayHi(t: T): String } object CanSayHi { def apply[T](implicit C: CanSayHi[T]): CanSayHi[T] = C } implicit class CanSayHiSyntax[T: CanSayHi](t: T) { def greet: String = CanSayHi[T].sayHi(t) } final case class Player(name: String, var level: Int) object Player { implicit val playerGreeter: CanSayHi[Player] = new CanSayHi[Player] { def sayHi(t: Player): String = s"Hi, I'm player ${t.name}, I'm lvl ${t.level} !" } }
  • 32. LA BOITE À OUTILSLA BOITE À OUTILS
  • 33. SIMULACRUMSIMULACRUM permet de se débarasser du boiler plate en le générant automatiquement, à la compilation, grâce à des macros Simulacrum import simulacrum._ @typeclass trait CanSayHi[T] { @op("greet") def sayHi(t: T): String }
  • 34. MAGNOLIAMAGNOLIA permet la dérivation automatique de type classes pour les ADTs Product types: Sum types: Si A et B sont des instances d'une type class T, alors C l'est aussi, "automatiquement" ! Magnolia type A type B final case class C(a: A, b: B) sealed trait C final case class A() extends C final case class B() extends C
  • 35. ALLER PLUS HAUT !ALLER PLUS HAUT ! FP resources list Anatomy of a type class Inheritance vs Generics vs TypeClasses in Scala Mastering Typeclass Induction Type class, ultimate ad hoc Type classes in Scala Implicits, type classes, and extension methods
  • 36. CONCLUSIONCONCLUSION Les type classes permettent: De ne pas mixer comportements et donnée L'ad hoc polymorphism D'ajouter du comportement à un type à posteriori