Searching...
sábado, 29 de julio de 2023

Pattern matching en Scala

 Pattern matching, es un mecanismo usado en programación funcional que permite evaluar un objeto/valor/expresión contra una serie de valores/objetos/expresiones, es como un switch con esteroides ya que un switch sólo soporta algunos tipos en la evaluación (cadenas y enteros) por lo que resulta ser una versión más poderosa que el switch de Java o incluso que usar una serie de sentencias if-else, porque la coincidencia exitosa del valor contra el patrón definido puede deconstruir el valor en sus partes constituyentes.

Scala match vs Java switch

El pattern matching permite deconstruir un objeto y evaluarlo.

Veamos un ejemplo:

import scala.util.Randomval x: Int = Random.nextInt(10)x match {
case 0 => "zero"
case 1 => "one"
case 2 => "two"
case _ => "many"
}

En éste ejemplo podemos observar el uso del pattern matching en un valorpara deconstruir ese valor y “reaccionar” a él. La sintaxis es selector match { case clauses } donde selector es el valor y las case clauses son las alternativas que queremos comparar para hacer match. Es posible definir una expresión match y definir un valor de retorno de la siguiente forma:

def matchTest(x: Int): String = x match {   
case 1 => "one"
case 2 => "two"
case _ => "many"
}
matchTest(3) // many
matchTest(1) // one

De esta forma podemos reutilizar la expresión y usar su valor de retorno en otra expresión. Pattern matching se puede utilizar en distintos tipos de patrones (case clauses) como: constantes, variables, secuencias, tuplas, tipos, case classes, etc. Veamos algunos ejemplos de esto:

Sequences (Lists o Arrays)

expr match { 
case List(0, _, _) => println("found it")
case _ =>
}
expr match { // use _* to avoid specifying how long it can be
case List(0, _*) => println("found it")
case _ =>
}

Tuples

def tupleDemo(expr: Any) = expr match {
case (a, b, c) => println("matched "+ a + b + c)
case _ =>
}
tupleDemo(("a ", 3, "-tuple")) // prints matched a 3-tuple

Types

def generalSize(x: Any) = x match {
case s: String => s.length
case m: Map[_, _] => m.size
case _ => -1
}
generalSize("abc) // returns Int = 3
generalSize(Map(1 -> 'a', 2 -> 'b')) // returns Int = 2

En los ejemplos anteriores se describe la simplicidad con la que podemos hacer uso de esta herramienta para diferentes tipos, la variedad de patrones que podemos encontrar puede enriquecer y simplificar lo que deseamos expresar en nuestro código, como el caso de los “types”, donde hacemos match por el tipo de dato para crear una expresión generalizada de tamaños de datos.

Ahora veamos un ejemplo con case clases.

abstract class Notificationcase class Email(sender: String, title: String, body: String) extends Notificationcase class SMS(caller: String, message: String) extends Notificationcase class VoiceRecording(contactName: String, link: String) extends Notificationdef showNotification(notification: Notification): String = {
notification match {
case Email(email, title, _) =>
s"You got an email from $email with title: $title"
case SMS(number, message) =>
s"You got an SMS from $number! Message: $message"
case VoiceRecording(name, link) =>
s"you received a Voice Recording from $name! Click the link to hear it: $link"
case _ => "No notification"
}
}
val someSms = SMS("12345", "Are you there?")
val someVoiceRecording = VoiceRecording("Tom", "voicerecording.org/id/123")
println(showNotification(someSms)) // prints You got an SMS from 12345! Message: Are you there?println(showNotification(someVoiceRecording)) // you received a Voice Recording from Tom! Click the link to hear it: voicerecording.org/id/123

Pattern matching en case classes es muy útil y dota de mucha expresión sobre lo que queremos hacer con poco código, además de que no sólo podemos hacer case clauses sobre el tipo de valor sino sobre su estructura como es el caso de case Email(...) donde no nos importa el body del email sino sólamente hacer match con el email y el título, de esta forma podemos elegir sobre qué atributos hacer match. Otra cosa a destacar es la forma tan simple en la que hemos creado un método o función para visualizar notificaciones, un problema muy común hoy en día, pues ¿en cuántas páginas en internet o aplicaciones en nuestro celular no hemos visto notificaciones aparecer?, todas de distintos tipos pero siempre visualizadas de la misma forma, sólo basta pensar en un modelo de datos tan simple como el que muestra el ejemplo y lo demás viene por añadidura en el lenguaje.

Y así como creamos case clauses para case classes, podemos hacerlo para otros tipos siendo pattern matching una herramienta tan conveniente pues todoslos tipos pueden figurar para hacer match. Un ejemplo de esto puede ser el uso de pattern matching para manejar nuestras excepciones de una forma más funcional, en vez de sólo lanzar la excepción y terminar de forma inadecuada podemos crear case clauses que retornen un valor equivalente al error que estamos encontrando. Podemos “validar” el contenido de una variable, para saber si una función está recibiendo los parámetros adecuados o también el valor de retorno de una función por ejemplo en el caso de ser de tipo Option donde simplemente indicamos que puede contener “algo” lo cual sería un tipo Some, o no contener “algo” que sería un None, pero esto podemos verlo con más detalle en un siguiente post sobre Scala.

Por último quiero explicar los case clauses de los ejemplos donde agregamos un _ (guión bajo), esta notación llamada wildcard hace match con cualquier objeto en absoluto y puede servir de distintas formas según sea la necesidad, la principal puede ser simplemente que queremos descartar cualquier otro valor, todo lo que no haga match con los casos anteriores hará match con el wildcard, en nuestro último ejemplo se utiliza para determinar si el valor enviado a la función showNotification() no es una notificación y al tener como valor de retorno un String simplemente retornamos un mensaje que dice que no es una notificación.

¿Te diste cuenta de la notación en los Strings que creamos para cada caso? s”Message $value some other $value2”, en Scala esta notación es conocida como string interpolation que simplemente nos permite embeber la referencia de una variable o valor dentro de las literales String (una forma elegante de crear Strings), y esto es posible gracias a el interpolator “s” que está antes de la literal; existen otros interpolators y nosotros podríamos crear el nuestro :)

Pattern matchingOptionswildcardsinterpolators, son sólo algunas cosas más que hacen a Scala un lenguaje elegante, y más allá de elegante, simple en su forma de expresar código.

Referencias

0 comentarios:

Publicar un comentario

Gracias por participar en esta página.

 
Back to top!