Les extracteurs en Scala

Leave a Comment

Les extracteurs sont une fonctionnalité de Scala permettant de faire du pattern matching sur un objet et d’en extraire des propriétés. On a cette fonctionnalité gratuitement avec les case classes mais pas pour les autres types de classes.

Voici rapidement comment cela se passe avec les case classes. Supposons que nous avons une classe Person définie comme suit:

case class Person(firstName: String, lastName: String)

Nous avons une liste de personnes et souhaitons afficher seulement celles dont le nom est “Cohen” (une simple opération de filtrage quoi ;-)). Voici une solution avec le pattern matching et le fait que Person est une case classe.

val persons = List(Person("Elie", "Cohen"), Person("John", "Dupont"), 
                   Person("Steve", "Woz"), Person("Daniel", "Cohen"))
       
persons foreach { person: Person =>
  person match {
    case Person(_, "Cohen") => println(person)
    case _ => ()
 }
}

Cool tout ça! Mais si notre classe n’est pas une case classe que faisons-nous alors? Les extracteurs viennent à notre secours. Pour illustrer mes propos je vais prendre un exemple volontairement simpliste: une application qui affiche des e-mails.

trait Message

class EmailMessage(
  val from: String,
  val to: String,
  val subject: String,
  val content: String
 ) extends Message

Le composant chargé de l’affichage est défini comme suit:

trait MessageDisplayer {
  def display(messages: Seq[Message])
}

Voici une implémentation du trait MessageDisplayer:

class PrintStreamMessageDisplayer(out: PrintStream) extends MessageDisplayer {
  def display(messages: Seq[Message]) {
    messages foreach { message: Message =>
      message match {
        case EmailMessage(from, to, subject, content) =>  
          out.println("============= EMAIL =============")
          out.println("Subject: " + subject)
          out.println("From : " + from)  
          out.println(content)  
        case _ @ unknownMessageType =>
          out.println("Unkown message type" + unknownMessageType)
      }
    }
  }
}

Cette implémentation utilise un objet PrintStream pour afficher les messages. Notez l’usage du pattern matching sur EmailMessage. Ceci est possible grâce à la définition de la méthode unapply() dans l’objet compagnon (campanion object) de la classe EmailMessage.
Cette méthode est définie comme suit:

def unapply(email: EmailMessage) =
  Some(email.from, email.to, email.subject, email.content)

Elle prend en paramètre un objet de type EmailMessage et retourne objet Some contenant les propriété du message EmailMessage. Cette méthode est invoquée lorsque nous faisons :

message match {
        case EmailMessage(from, to, subject, content) =>  …
        ….
} 

Il existe une autre méthode apply() que l’on peut définir dans l’objet compagnon. Celle-ci permet de créer un objet EmailMessage avec la syntaxe :

val email = EmailMessage("tota@toto.com", 
                         "tata@toto.com", 
                         "Hello!", 
                         "Hi! How do you do?")

Remarquez l’absence de new dans le snippet précédent, il invoque la méthode apply() de l’objet compagnon avec les arguments fournis et celle-ci s’occupe des details de création de l’objet.
Le bout de code précédent est équivalent à

val email = EmailMessage.apply("tota@toto.com",
                         "tata@toto.com",
                         "Hello!",
                         "Hi! How do you do?") 

Code final d’EmailMessage:

class EmailMessage(
 val from: String,
 val to: String,
 val subject: String,
 val content: String
  ) extends Message

object EmailMessage {
  def apply(from: String, to: String, subject: String, content: String) =
 new EmailMessage(from, to, subject, content)
  def unapply(email: EmailMessage) =
 Some(email.from, email.to, email.subject, email.content)
}

PrintStreamMessageDisplayer et son campanion object:

class PrintStreamMessageDisplayer(out: PrintStream) 
     extends MessageDisplayer {
  def display(messages: Seq[Message]) {
    messages foreach { message: Message =>
      message match {
        case EmailMessage(from, to, subject, content) =>  
          out.println("============= EMAIL =============")
          out.println("Subject: " + subject)
          out.println("From : " + from)  
          out.println(content)  
        case _ @ unknownMessageType =>
          out.println("Unkown message type" + unknownMessageType)
      }
    }
  }
}

object PrintStreamMessageDisplayer {
  def apply(out: PrintStream) = new PrintStreamMessageDisplayer(out)
}

Enfin le code pour tester tout ça:
object ExtractorsTutorial extends App {
  val emails = Seq(
    EmailMessage("tota@toto.com", "tata@toto.com", "Hello!", "Hi! How do you do?"),
    EmailMessage("john.doe@noname.com", "jane@who.com", "Hello!", "Dear Jane! Who am I ?"),
    EmailMessage("madmax@domain.com", "borat@example.com", "Go", "Get out the way."))

    val displayer = PrintStreamMessageDisplayer(Console.out)
    displayer.display(emails)
}

Son exécution donne:
============= EMAIL =============
Subject: Hello!
From : tota@toto.com
Hi! How do you do?
============= EMAIL =============
Subject: Hello!
From : john.doe@example.com
Dear Jane! Who am I ?
============= EMAIL =============
Subject: Go
From : madmax@domain.com
Get out the way. 

Fin de voyage pour cette première rencontre avec les extracteurs.

© Nouhoum TRAORE.. Fourni par Blogger.