Couritas Etienne
@courieti
par Dave Gurnell
@davegurnell
Il y a même une traduction française !
Séparer les fonctionalités de la donnée
trait Ordering[T] {
def compare(x: T, y: T): Int
}
def prettyCompare[T](x : T, y : T)
(implicit ordering : Ordering[T]) = {
ordering.compare(x,y) match {
case 0 => "x est égal à y"
case i if i > 0 => "x est plus grand que y"
case i if i < 0 => "x est plus petit que y"
}
}
implicit val intOrdering : Ordering[Int] =
new Ordering[Int] {
def compare(x: Int, y: Int): Int = {
Integer.compare(x, y)
}
}
sealed trait Forme
final case class Rectangle(largeur: Double, hauteur: Double) extends Forme
final case class Cercle(rayon: Double) extends Forme
Une simple liste de types
mais il s'agit d'un type et non d'une instance
HList
Soit la liste est vide :
Soit la liste a un élément et une queue :
Head est un type abitraire
HNil
::[A,B <: HList]
case class IceCream(name: String, numCherries: Int, inCone: Boolean)
String :: Int :: Boolean :: HNil
Utilisation de shapeless.Generic avec un product
import shapeless.Generic
val iceCreamGen = Generic[IceCream]
val iceCream = IceCream("Sunday", 1, false)
val repr = iceCreamGen.to(myIceCream)
//repr : iceCreamGen.Repr = Sunday :: 1 :: false :: HNil
val iceCream2 = iceCreamGen.from(repr)
// iceCream2 : IceCream(Sunday, 1, false)
sealed trait Forme
final case class Rectangle(largeur: Double, hauteur: Double) extends Forme
final case class Cercle(rayon: Double) extends Forme
Rectangle :+: Cercle :+: CNil
import shapeless.Generic
val gen = Generic[Forme]
val rect = gen.to(Rectangle(1.0,2.0))
// rect : gen.Repr = Inl(Rectangle(1.0, 2.0))
val circle = gen.to(Cercle(1.0))
// circle : gen.Repr = Inr(Inl(Cercle(1.0)))
Revenons à notre type classe
trait Ordering[T] {
def compare(x: T, y: T): Int
}
Commençons par definir quelques outils
object Ordering {
import shapeless.{::, HList, HNil}
//summoner method
def apply[A](implicit ordering: Ordering[A]) = ordering
def createOrdering[T](f : (T,T) => Int) =
new Ordering[T] {
def compare(a: T, b: T) : Int = f(a,b)
}
implicit class OrderingOps[T](a : T)(implicit ord : Ordering[T]){
def compare(b: T) : Int = ord.compare(a,b)
}
}
implicit val intOrdering : Ordering[Int] =
createOrdering((a,b) => a.compare(b))
implicit val doubleOrdering : Ordering[Double] =
createOrdering((a,b) => a.compare(b))
implicit val stringOrdering : Ordering[String] =
createOrdering((a,b) => a.compare(b))
implicit val booleanOrdering : Ordering[Boolean] =
createOrdering((a,b) => a.compare(b))
implicit val hNilOrdering : Ordering[HNil] =
createOrdering((a,b) => 0)
Implicit Scope
Ordering[ Int ]
Ordering[ String ]
Ordering[ Boolean ]
Ordering[ Double ]
Ordering[ HNil ]
Ordering[ H :: T ]
Ordering[String :: Int :: Boolean :: HNil]
Ordering[String]
Ordering[Int :: Boolean :: HNil]
Ordering[String]
Ordering[Boolean :: HNil]
Ordering[Int]
Ordering[String]
Ordering[Int]
Ordering[Boolean]
Ordering[HNil]
L'instance d'ordering pour HList
implicit def hListOrdering[H, T <: HList](
implicit
hOrdering: Ordering[H],
tOrdering: Ordering[T]
) : Ordering[H :: T] =
createOrdering((a,b) => {
hOrdering.compare(a.head,b.head) match {
case 0 => tOrdering.compare(a.tail,b.tail)
case r => r
}
})
sealed trait Tree[T]
final case class Branch[T]( left : Tree[T], right: Tree[T]) exends Tree[T]
final case class Leaf[T]( value : T ) exends Tree[T]
//1
Ordering[Tree[Int]]
//2
Ordering[Branch[Int] :+: Leaf[Int] :+: Cnil]
//3
Ordering[Branch[Int]]
//4
Ordering[Tree[Int] :: Tree[Int] :: HNil]
//5
Ordering[Tree[Int]] //Outch!
L'instance d'ordering pour HList avec lazy
implicit def hListOrdering[H, T <: HList](
implicit
hOrdering: Lazy[Ordering[H]],
tOrdering: Ordering[T]
) : Ordering[H :: T] =
createOrdering((a,b) => {
hOrdering.value.compare(a.head,b.head) match {
case 0 => tOrdering.compare(a.tail,b.tail)
case r => r
}
})
Maintenant comment convertir nos case classes en HList
implicit def genericOrdering[T](
implicit
gen : Lazy[Generic[T]],
ordering : Ordering[gen.Repr]): Ordering[T] =
{
createOrdering((a: T,b: T) =>
ordering.value.compare(gen.to(a), gen.to(b))
)
}
Generic[T] contien un membre Repr qui est la représentation générique de T
en somme gen.Repr est une HList
Ce que l'on veut c'est un Ordering[gen.Repr]
implicit def genericOrdering[T](
implicit
gen : Generic[T],
ordering : Ordering[gen.Repr]
): Ordering[T]
Malheureusement le compilateur ne nous permet pas d'accéder à un membre de type d'un autre paramètre.
Mais il y a une solution.
implicit def genericOrdering[T, H <: HList](
implicit
gen : Generic[T]{ type Repr = H},
ordering : Ordering[H]
): Ordering[T]
On peut faire l'analogie avec une fonction
Generic[T]{ type Repr = H}
T => H
est un alias de
Generic[T]{ type Repr = H}
Generic.Aux[T, H]
On peut imaginer comparer des éléments d'une famille scellée entre eux. Pour l'exemple on utilisera juste l'ordre de déclaration.
Comme pour HList la définition de l'élément est très simple.
implicit def cNilOrdering: Ordering[CNil] =
createOrdering((a,b) =>
throw new Exception("imposiburu!")
)
/!\ Il n'y a pas d'instance de CNil donc ce code ne sera jamais éxécuté.
Il est juste là pour aider à la compilation.
implicit def coproductOrdering[H, T <: Coproduct] (
implicit
hOrdering : Lazy[Ordering[H]],
tOrdering : Ordering[T]
): Ordering[H :+: T] =
createOrdering ((a,b) =>
(a,b) match {
case (Inl(a), Inl(b)) => hOrdering.value.compare(a,b)
case (Inr(a), Inr(b)) => tOrdering.compare(a,b)
case (Inr(a), Inl(b)) => 1
case (Inl(a), Inr(b)) => -1
}
)
Traitement similaire aux HList
Utilisation
scala> val a : Forme = Cercle(2.0)
a: Forme = Cercle(2.0)
scala> val b : Forme = Cercle(1.0)
b: Forme = Cercle(1.0)
scala> a.compare(b)
res0: Int = 1
sealed trait Forme
final case class Rectangle(largeur: Double, hauteur: Double) extends Forme
final case class Cercle(rayon: Double)extends Forme
scala> val c: Forme = Rectangle(1.0, 2.0)
c: Forme = Rectangle(1.0,2.0)
scala> c.compare(a)
res3: Int = 1
scala> c.compare(c)
res4: Int = 0
La version simplifiée du résultat dans la console
scala> LabelledGeneric[Rectangle]
res10: shapeless.LabelledGeneric[Rectangle]{
type Repr = "largeur" ->> Double :: "hauteur" ->> Double :: HNil
} = shapeless.LabelledGeneric$$anon$1@6306fc65
Shapeless Ops.
Défini dans le package shapeless.ops
C'est un ensemble de type classes prédéfinies, « Fonctions à types dépendants »
Pour HList cela ressemble à l'api des iterables
Utilisation récursive d'`Implicitly` à la recherche de la typeclass manquante
Utiliser `reify`, s'il y a encore des types génériques ou des Any/Nothing, c'est qu'il y a un problème
@courieti
https://github.com/crakjie/shapeless-guide