Ce diaporama a bien été signalé.
Nous utilisons votre profil LinkedIn et vos données d’activité pour vous proposer des publicités personnalisées et pertinentes. Vous pouvez changer vos préférences de publicités à tout moment.

Anatomie d'une typeclass

143 vues

Publié le

Le pourquoi et le comment des type classes en Scala.

Publié dans : Ingénierie
  • Soyez le premier à commenter

  • Soyez le premier à aimer ceci

Anatomie d'une typeclass

  1. 1. ANATOMIE D'UNEANATOMIE D'UNE TYPE CLASSTYPE CLASS
  2. 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. 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. 4. RELATION DONNÉE / COMPORTEMENT:RELATION DONNÉE / COMPORTEMENT: DIVERGENCE DE POINTS DE VUE !DIVERGENCE DE POINTS DE VUE !
  5. 5. SÉPARATION DONNÉE / COMPORTEMENTSÉPARATION DONNÉE / COMPORTEMENT La programmation orientée objet et la programmation fonctionnelle: deux approches opposées !
  6. 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. 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. 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. 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. 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)
  11. 11. POLYMORQUOI ?POLYMORQUOI ?
  12. 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. 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. 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. 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. 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. 17. ANATOMIE DE LAANATOMIE DE LA TYPE CLASSTYPE CLASS
  18. 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. 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. 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. 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. 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. 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. 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. 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. 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. 27. TYPE CLASSTYPE CLASS MECA-AUGMENTÉE !MECA-AUGMENTÉE !
  28. 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. 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. 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. 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. 32. LA BOITE À OUTILSLA BOITE À OUTILS
  33. 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. 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. 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. 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
  37. 37. MERCI !MERCI ! @MMENESTRET@MMENESTRET GEEKOCEPHALE.COMGEEKOCEPHALE.COM

×