Le pattern matching est une technique puissante permettant de filtrer et de tester la valeur de variables. Le but de cet article est d’illustrer par l'exemple le pattern matching en Scala.
Supposons que nous voulions créer une fonction retournant la couleur (noir / rouge) d'une enseigne d'une carte à jouer (les quatre enseignes du jeu français étant : trèfle, cœur, carreau et pique).
Notes : dans le code ci-dessous, les commentaires se trouvant à la fin de chaque ligne correspondent à la sortie standard de la console ; j'ai utilisé un worksheet IntelliJ IDEA pour coder interactivement cet exemple, mais on peut bien sûr utiliser la console Scala.
1ère étape : création d'une simple classe
Commençons par créer une classe immuable représentant l'enseigne "trèfle" :
class Trèfle {
val symbole = "♣"
val libellé = "trèfle"
}
val trèfle: Trèfle = new Trèfle()
trèfle.symbole // ♣
Notons que cette classe permet de comparer des objets par identité (référence) et non par valeur car on ne surcharge pas la méthode equals
:
trèfle.equals(trèfle) // true
trèfle == new Trèfle // false
trèfle.eq(new Trèfle) // false
trèfle.equals(new Trèfle) // false
De même, les méthodes hashCode
, toString
sont celles par défaut :
trèfle // Trèfle@1ac88f64
trèfle.hashCode() // 123717365
new Trèfle().hashCode() // 1323753063
2ème étape : création d'une case class
La création d'une case class
permet :
- d'auto-implémenter les méthodes
equals
,hashCode
ettoString
; - de bénéficier de deux méthodes utiles au pattern matching : un "constructeur", la méthode
apply
, et un "dé-constructeur", la méthodeunapply
.
case class Enseigne(symbole: String, libellé: String)
Nous avons un constructeur "gratuit", la méthode apply
:
Enseigne("♣", "trèfle") // Enseigne(♣,trèfle)
Nous avons également une implémentation "gratuite", basée sur les champs, des méthodes equals
, hashCode
et toString
:
Enseigne("♣", "trèfle").symbole // ♣
Enseigne("♣", "trèfle") == Enseigne("♣", "trèfle") // true
Enseigne("♣", "trèfle").equals(Enseigne("♣", "trèfle")) // true
Enseigne("♣", "trèfle").eq(Enseigne("♣", "trèfle")) // false
Enseigne("♣", "trèfle").hashCode() // 841520215
Enseigne("♣", "trèfle").hashCode() // 841520215
Etape "bonus" : création d'une énumération
Puisque nous avons quatre enseignes, nous pouvons créer une énumération. Ca ne servira pas directement à implémenter notre exemple final mais l'exemple s'y prête bien.
object Enseignes {
val TREFLE = Enseigne("♣", "trèfle")
val CARREAU = Enseigne("♦", "carreau")
val COEUR = Enseigne("♥", "coeur")
val PIQUE = Enseigne("♠", "pique")
def values() = List(CARREAU, COEUR, PIQUE, TREFLE)
}
Enseignes.TREFLE != Enseignes.CARREAU // true
Enseignes.values // List[Enseigne] = List(Enseigne(♦,carreau), Enseigne(♥,coeur), Enseigne(♠,pique), Enseigne(♣,trèfle))
Dernière étape : pattern matchons !
Premier exemple
Un premier exemple de pattern matching, utilisé dans une fonction qui retourne la couleur de l'enseigne :
def indiquerJusteLaCouleur(cardSuite: Enseigne): String = cardSuite match {
case Enseignes.TREFLE | Enseignes.PIQUE => "noir"
case Enseignes.CARREAU | Enseignes.COEUR => "rouge"
case _ => "aucune"
}
indiquerJusteLaCouleur(Enseignes.PIQUE) // noir
Ce premier exemple met en avant :
- la notation
|
(disjunction) permettant de regrouper plusieurs cas ; - la notation
_
(wildcard) pour gérer les autres cas.
Deuxième exemple
Voici un deuxième exemple montrant comment récupérer sélectivement certains champs (ici, le libellé de l'enseigne) en "dé-structurant" notre instance de case class
, via la méthode unapply
.
def décrireLaCouleur(enseigne: Enseigne): String = enseigne match {
case Enseigne(_, libellé) => s"$libellé is ${indiquerJusteLaCouleur(enseigne)}"
}
décrireLaCouleur(Enseignes.PIQUE) // pique est noir
Et voilà ! 🤓
PS : merci à Jérôme Prudent pour ses tuyaux en Scala et ses avis éclairés. Jérôme est contributeur au blog d'Arolla (lien direct vers ses articles).