Blog Arolla

Les type classes Scala : exemple sur une sérialisation MongoDB (2/2)

Dans la première partie, nous avons introduit les types classes et avons créé une API pour travailler avec MongoDB en Scala. Dans cette partie, nous allons voir comment améliorer cette API grâce aux paramètres implicites.

Scala donne la possibilité d’annoter les paramètres d’une méthode comme étant des paramètres “implicites”. Un paramètre implicite d’une méthode est un paramètre que nous ne sommes pas obligés de passer à la méthode lors de son invocation. Dans le cas où un paramètre implicite n’est pas passé en argument de la méthode, le compilateur Scala se débrouille pour en déterminer un. Appliqué à la méthode save de MongoOperations, nous obtenons la déclaration suivante :

trait MongoOperations {
  def save[T](t: T)(implicit tc: MongoDbModel[T], coll: String => MongoCollection): Unit
    = tc.write(t) map { dbo => coll(tc.collectionName).insert(dbo) }
  ...
}

L’annotation implicit s’applique à la liste de paramètres qui la suivent, c’est-à-dire à la fois à l’instance de la type classe tc et à la fonction coll. A l’usage, cela donne :

...
implicit def collFun = (name: String) => MongoConnection()("test")(name)
//Sauvegarde
save(User(new ObjectId, "Toto"))
save(Task(new ObjectId, "A first task", false))
val user = findOne[User](DBObject("_id" -> new ObjectId("50033d4344aef87888c08537")))
//Récupération
val task = findOne[Task](DBObject("_id" -> new ObjectId("50033d4344aef87888c08537")))

Maintenant, essayons de répondre à la question vous devez être en train de vous poser : comment le compilateur fait pour trouver les paramètres manquants ?

Prenons par exemple cette ligne :

save(Task(new ObjectId, "A first task", false))

Lorsque le compilateur se trouve face à cet appel, il cherche à  résoudre les paramètres implicites en regardant d’abord dans le scope courant. Il y trouve la fonction collFun marquée comme implicite et l’utilise à la place du dernier paramètre de la méthode save, la fonction qui renvoie une collection MongoDB à partir de son nom. Par contre, rien n’est défini dans le scope local pour le deuxième paramètre, l’instance de la type classe et le compilateur cherche ailleurs et finit par trouver TaskMongoModel, l’instance de la type classe, définie comme objet implicite dans l’objet compagnon de la classe Task :

object Task {
  implicit object TaskMongoModel extends MongoDbModel[Task] {
    def collectionName = "tasks"
    def write(task: Task): Option[DBObject] = try {
      Some(DBObject("_id" -> task.id, "title" -> task.title, "isDone" ->
      task.isDone))
    } catch {
      case _ => None
    }

    def read(dbo: DBObject): Option[Task] = try {
      Some(Task(dbo.as[ObjectId]("_id"), dbo.as[String]("title"),
        dbo.as[Boolean]("isDone")))
    } catch {
      case _ => None
    }
  }
}

Si à la fin, le compilateur n’arrive pas à déterminer une valeur pour un paramètre implicite, une erreur est produite.

Avec cette nouvelle version de notre API, nous n’avons plus besoin de fournir explicitement les paramètres qui alourdissent notre code. Nous avons tiré profit des paramètres implicites à cet effet.

Pour finir...

Avant de conclure, faisons un petit détour par la classe Collections de Java et notamment les méthodes sort dont elle dispose :

static <T extends Comparable<? super T>> void sort(List<T> list)
static <T> void sort(List<T> list, Comparator<? super T> c)

La première méthode trie les éléments de la liste mais impose que leur type implémente l’interface Comparable de Java. Cela pose plusieurs problèmes.

Le premier est que si vous devez trier une liste d’objets dont le type n’implémente pas Comparable et que vous n’avez pas la main sur le type en question, vous ne pourrez pas trier votre liste. Le deuxième est qu’il n’est pas possible d’implémenter deux logiques de tri pour le même type avec cette méthode.

La deuxième méthode de tri définie par la classe Collections résout les problèmes mentionnés ci-dessus en sortant la logique de tri de la hiérarchie du type. Elle prend en paramètre une liste d’éléments d’un type quelconque T et un objet de type Comparator qui permet de définir une relation d’ordre entre les dits éléments. C’est l’approche des types classes ! Vous l’aviez peut-être trouvé !

Ce que nous venons de voir n’est qu’une introduction aux types classes. Elles sont utilisées à titre d’exemples dans casbah, scalaz, sjson. Vous serez étonné de leur puissance si vous regardez ce qu’elles permettent de faire avec Scalaz.

Références

Plus de publications

2 comments for “Les type classes Scala : exemple sur une sérialisation MongoDB (2/2)

  1. Pingback: Discussion avec Nouhoum Traoré | Arolla