Pattern Matching es el concepto asociado al chequeo estructural de un dato respecto de una estructura esperada. El uso de pattern matching tiene la ventaja de simplificar mucho la codificación, ya que sólo escribimos la forma de lo que esperamos y podemos desglosar los componentes de estructuras complejas.
Una desventaja asociada a usar pattern matching en vez de funciones para acceder a los elementos de las tuplas es que nuestro código se verá muy afectado ante un cambio en las estructuras manejadas. El simple hecho de agregar un elemento más a la tupla que representa a nuestro elemento de dominio provocará un cambio en el tipo del mismo y el mismo se propagará en todos los lugares donde se haya utilizado esta estructura directamente.
Si queremos saber si el nombre de un día de la semana es fin de semana podemos hacer:
esFinDeSemana dia = dia == "Sábado" || dia == "Domingo"
Sin embargo esta otra definición también es válida:
esFinDeSemana' "Sábado" = True
esFinDeSemana' "Domingo" = True
esFinDeSemana' _ = False
La última definición es necesaria, ya que a diferencia del paradigma lógico, no contamos con el principio de universo cerrado y si sólo definimos esFinDeSemana’ para “Sábado” y “Domingo” y consultamos por otro día, Haskell nos mostrará un error porque el patrón no calza con ningún elemento del dominio esperado. Para que esta función funcione correctamente es importante que el encabezado con la variable anónima, que matchea con cualquier patrón, sea la última definición de esFinDeSemana’, de lo contrario no habrá ningún elemento del dominio cuya imagen pueda ser True (por unicidad).
Acá tenemos un ejemplito recursivo típico para pattern matching con números:
factorial 0 = 1
factorial n = n * factorial (n - 1)
Para evitar loops infinitos, es importante poner el caso base primero para que matchee con el 0, ya que la variable n también matchearía con este valor.
Si tengo los valores
v1 = (2,5)
v2 = (3,5)
v3 = (8,5)
El patrón (x,5)
matchea con los tres valores.
También el patrón (x,y)
matchea con los tres valores.
También el patrón (_,5)
matchea con los tres valores.
También el patrón (_,_)
matchea con los tres valores.
También el patrón unaTupla
matchea con los tres valores.
Ahora, si tengo una lista
lista1 = [(2,5),(5,3),(3,3)]
lista2 = [(2,5)]
El patrón (x,5)
no matchea con lista1
ni con lista2
. ¡Es una tupla, no una lista! Antes de tratar de matchear, Haskell nos va a tirar un error de tipos.
El patrón (x:xs)
matchea con lista1
, siendo x = (2,5)
y xs = [(5,3),(3,3)]
y matchea con lista2
, siendo x = (2,5)
y xs = []
El patrón [x]
no matchea con lista1
porque tiene más de un elemento, pero sí matchea con lista2
, siendo x = (2,5)
El patrón (x:_)
matchea con lista1
, siendo x = (2,5)
y matchea con lista2
, siendo x = (2,5)
El patrón (x:y:_)
matchea con lista1
, siendo x = (2,5)
e y = (5,3)
, y no matchea con lista2
porque sólo tiene un elemento
El patrón unaTupla
matchea con lista1
siendo unaTupla = [(2,5),(5,3),(3,3)]
y matchea con lista2
siendo unaTupla = [(2,5)]
. Es sólo un nombre de variable, con lo cual no restringe realmente el tipo del valor para que sea una tupla.
(En algunos cursos no se da data, para más información ver acá: Data: Definiendo nuestros tipos en Haskell) Suponiendo que tenés
data Coordenada = Coord Int Int
(el tipo es Coordenada y el constructor es Coord)
Donde se puede aplicar la misma idea
lista1 = [Coord 2 5,Coord 5 3,Coord 3 3]
lista2 = [Coord 2 5]
El patrón (Coord x 5)
no matchea con lista1
ni con lista2
. ¡Es una Coordenada, no una lista! Antes de tratar de matchear, Haskell nos va a tirar un error de tipo.
El patrón (Coord x y:restoCoords)
matchea con lista1
, siendo x = 2
, y = 5
y restoCoords = [Coord 5 3,Coord 3 3]
y matchea con lista2
, siendo x = 2
, y = 5
y restoCoords = []
El patrón [x]
no matchea con lista1
pero si matchea con lista2
, siendo x = Coord 2 5
El patrón (x:_)
matchea con lista1
y matchea con lista2
siendo x = Coord 2 5
El patrón (x:y:_)
matchea con lista1
siendo x = Coord 2 5
y y = Coord 5 3
pero no matchea con lista2
El patrón unaTupla
matchea con lista1
y matchea con lista2
porque no es más que una variable sin ninguna restricción después de todo
Usamos este patrón para definir un sinónimo, o sea necesito por un lado el patrón para tomar algunos de sus componentes y por otro todo junto para la hacer otra cosa, pongamos un par de ejemplos:
ordenada [_] = True
ordenada (x1:x2:xs) = x1 > x2 && ordenada x2:xs
Puede mejorarse sutilmente con un sinónimo.
ordenada [_] = True
ordenada (x1:xs@(x2:_)) = x1 > x2 && ordenada xs
Otro ejemplo, entre dos complejos representados con tuplas, obtener el que tiene mayor parte real:
mayorParteReal (a@(a1, _)) (b@(b1, _))
| a1 > b1 = a`
| otherwise = b
Y otro con listas por comprensión:
promedioDeAprobados alumnos = promedio [ promedio notas | alumno@(_,notas) <- alumnos, aprobo alumno ]
La variable anónima _ (guión bajo) funciona, en términos de matcheo, como una variable común:
esCero 0 = True
esCero x = False
Es lo mismo que decir:
esCero 0 = True
esCero _ = False
De hecho, esta última opción es mejor, porque ayuda a la expresividad: si la variable no se usa en la devolución (a la derecha del igual, ó a la derecha de la guarda) entonces mejor ni ponerle nombre. Por ejemplo:
anioActual = 2014
edad (nombre, anioNac, cantPerros) = anioActual - anioNac
En ese caso la función edad recibe una tupla pero a la derecha, en la definición de la función, no hace faltan el nombre y el cantPerros. Entonces, usamos variables anónimas:
edad ( _, anioNac, _) = anioActual - anioNac
Muchas veces pensamos que no necesitamos una variable, cuando en realidad sí la usamos a la derecha del igual. Por ejemplo, si queremos tomar esa definición de “persona” como una tupla de tres, y agregarle perros a la persona, debemos devolver a una persona, pero cambiando sólo la cantidad de perros.
Esto es erróneo:
agregarPerros cant (_, _, cantPerros) = (_, _, cantPerros + cant)
¡Son variables anónimas! Si quiero usarlas a la derecha del igual (ó sea, si quiero que sean parte de lo que devuelvo) entonces tengo que usar variables comunes.
Esto está bien:
agregarPerros cant (nombre, anioNac, cantPerros) = (nombre, anioNac, cantPerros + 1)