Anatomie d'une typeclass

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
1 sur 37

Recommandé

Interface collectionsinterInterface collectionsinter
Interface collectionsinterRYMAA
1.3K vues98 diapositives
Regex phpRegex php
Regex phpBertrand day
1.5K vues29 diapositives
Langage PerlLangage Perl
Langage PerlRached Krim
776 vues23 diapositives

Contenu connexe

Tendances

Ch13Ch13
Ch13yarsenv47
326 vues13 diapositives
Mémento camlMémento caml
Mémento camlzan
565 vues2 diapositives

Tendances(19)

Chapitre 2:  String en JavaChapitre 2:  String en Java
Chapitre 2: String en Java
Aziz Darouichi858 vues
Ch13Ch13
Ch13
yarsenv47326 vues
Mémento camlMémento caml
Mémento caml
zan565 vues
La programmation modulaire en PythonLa programmation modulaire en Python
La programmation modulaire en Python
ABDESSELAM ARROU3.7K vues
Introduction à scalaIntroduction à scala
Introduction à scala
SOAT2.7K vues
Chapitre1: Langage PythonChapitre1: Langage Python
Chapitre1: Langage Python
Aziz Darouichi2.4K vues
Polymorphisme : un concept polymorphe !Polymorphisme : un concept polymorphe !
Polymorphisme : un concept polymorphe !
Aurélien Regat-Barrel2.6K vues
JAVAJAVA
JAVA
Axel KAMALAK1.8K vues
chapitre1.pptchapitre1.ppt
chapitre1.ppt
Adel Madrid95 vues
Mort au boilerplate avec scala metaMort au boilerplate avec scala meta
Mort au boilerplate avec scala meta
Damien GOUYETTE65 vues
Bases de php - Partie 4Bases de php - Partie 4
Bases de php - Partie 4
Régis Lutter792 vues
Les fondamentaux du langage CLes fondamentaux du langage C
Les fondamentaux du langage C
Abdoulaye Dieng1.8K vues

Similaire à Anatomie d'une typeclass

Ruby Pour RoRRuby Pour RoR
Ruby Pour RoReric German
590 vues57 diapositives
Mix it 2011 - ClojureMix it 2011 - Clojure
Mix it 2011 - Clojurelolopetit
939 vues79 diapositives

Similaire à Anatomie d'une typeclass(20)

Ruby Pour RoRRuby Pour RoR
Ruby Pour RoR
eric German590 vues
Mix it 2011 - ClojureMix it 2011 - Clojure
Mix it 2011 - Clojure
lolopetit939 vues
Formation python micro club.netFormation python micro club.net
Formation python micro club.net
Zakaria SMAHI1.4K vues
Javascript un langage supérieurJavascript un langage supérieur
Javascript un langage supérieur
Fredy Fadel1.7K vues
JavaJava
Java
Simo Tkouki1.6K vues
Design poo togo_jug_finalDesign poo togo_jug_final
Design poo togo_jug_final
agnes_crepet726 vues
Design poo togo_jug_finalDesign poo togo_jug_final
Design poo togo_jug_final
Duchess France652 vues
Introduction au langage RubyIntroduction au langage Ruby
Introduction au langage Ruby
Julien Blin481 vues
cours1.pptcours1.ppt
cours1.ppt
RihabBENLAMINE6 vues
cours1.pptcours1.ppt
cours1.ppt
ssuser07fc0810 vues
cours2.pptcours2.ppt
cours2.ppt
asmachehbi4 vues
.php1 : les fondamentaux du PHP.php1 : les fondamentaux du PHP
.php1 : les fondamentaux du PHP
Abdoulaye Dieng1.4K vues
DroolsDrools
Drools
Klee Group4K vues
Php4 MysqlPhp4 Mysql
Php4 Mysql
HamdiBaklouti2.2K vues
Initiation au JavaScriptInitiation au JavaScript
Initiation au JavaScript
Mouna Dhaouadi106 vues

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