scala - software - Explica este código de coincidencia de patrones
scala vs java (3)
Este código proviene de la consulta de un conjunto de datos con el patrón de coincidencia de Scala :
object & { def unapply[A](a: A) = Some((a, a)) }
"Julie" match {
case Brothers(_) & Sisters(_) => "Julie has both brother(s) and sister(s)"
case Siblings(_) => "Julie''s siblings are all the same sex"
case _ => "Julie has no siblings"
}
// => "Julie has both brother(s) and sister(s)"
¿Cómo funciona &
realmente funciona? No veo una prueba booleana en ninguna parte para la conjunción. ¿Cómo funciona esta magia Scala?
Aquí es cómo funciona la aplicación en general:
Cuando tu lo hagas
obj match {case Pattern(foo, bar) => ... }
Pattern.unapply(obj)
. Esto puede devolver None
en cuyo caso la coincidencia del patrón es una falla, o Some(x,y)
en cuyo caso foo
y bar
están vinculados a x
e y
.
Si en lugar de Pattern(foo, bar)
Pattern(OtherPattern, YetAnotherPatter)
entonces x
coincidiría con el patrón OtherPattern
e y
se relacionaría con YetAnotherPattern
. Si todas esas coincidencias de patrón tienen éxito, el cuerpo de la coincidencia se ejecuta, de lo contrario, se intentará el siguiente patrón.
cuando el nombre de un patrón no es alfanumérico, sino un símbolo (como &
), se usa infijo, es decir, se escribe foo & bar
lugar de &(foo, bar)
.
Así que aquí hay un patrón que siempre devuelve Some(a,a)
sin importar lo que sea. Así, &
siempre hace coincidir y une el objeto coincidente con sus dos operandos. En código eso significa que
obj match {case x & y => ...}
siempre coincidirá y tanto x
como y
tendrán el mismo valor que obj
.
En el ejemplo anterior, esto se utiliza para aplicar dos patrones diferentes al mismo objeto.
Es decir cuando lo haces
obj match { case SomePattern & SomeOtherPattern => ...}`
Primero se aplica el patrón &
Como dije, siempre hace coincidir y vincula el objeto a su LHS y su RHS. Entonces, SomePattern
se aplica a la LHS de &
(que es lo mismo que obj
) y SomeOtherPattern
se aplica a la RHS de &
(que también es lo mismo que obj
).
Entonces, en efecto, solo aplicaste dos patrones al mismo objeto.
Para obtener información adicional, recomiendo leer la sección de Patrones de Operación Infix (8.1.10) de la Especificación del Idioma Scala .
Un patrón de operación de infijo
p op q
es una abreviatura del constructor o del patrón extractorop(p,q)
. La precedencia y la asociatividad de los operadores en los patrones es la misma que en las expresiones.
Que es prácticamente todo lo que hay, pero luego puede leer sobre los patrones de constructor y extractor y los patrones en general. Ayuda a separar el aspecto del azúcar sintáctico (la parte "mágica" de él) de la idea bastante simple de la coincidencia de patrones:
Un patrón se construye a partir de constantes, constructores, variables y pruebas de tipo. La coincidencia de patrones comprueba si un valor dado (o secuencia de valores) tiene la forma definida por un patrón y, si lo hace, vincula las variables del patrón a los componentes correspondientes del valor (o secuencia de valores).
Vamos a hacer esto desde el código. Primero, una pequeña reescritura:
object & { def unapply[A](a: A) = Some(a, a) }
"Julie" match {
// case Brothers(_) & Sisters(_) => "Julie has both brother(s) and sister(s)"
case &(Brothers(_), Sisters(_)) => "Julie has both brother(s) and sister(s)"
case Siblings(_) => "Julie''s siblings are all the same sex"
case _ => "Julie has no siblings"
}
La nueva reescritura significa exactamente lo mismo . La línea de comentarios está usando la notación de infijo para los extractores, y la segunda está usando la notación normal. Ambos se traducen a la misma cosa.
Por lo tanto, Scala alimentará a "Julie" al extractor, repetidamente, hasta que todas las variables no unidas sean asignadas a algo. El primer extractor es &
, por lo que obtenemos esto:
&.unapply("Julie") == Some(("Julie", "Julie"))
Tenemos Some
vuelta, por lo que podemos continuar con el partido. Ahora tenemos una tupla de dos elementos, y tenemos dos extractores dentro &
también, así que alimentamos cada elemento de la tupla a cada extractor:
Brothers.unapply("Julie") == ?
Sisters.unapply("Julie") == ?
Si ambos de estos devuelven algo, entonces el partido es exitoso. Solo por diversión, reescribamos este código sin la coincidencia de patrones:
val pattern = "Julie"
val extractor1 = &.unapply(pattern)
if (extractor1.nonEmpty && extractor1.get.isInstanceOf[Tuple2]) {
val extractor11 = Brothers.unapply(extractor1.get._1)
val extractor12 = Sisters.unapply(extractor1.get._2)
if (extractor11.nonEmpty && extractor12.nonEmpty) {
"Julie has both brother(s) and sister(s)"
} else {
"Test Siblings and default case, but I''ll skip it here to avoid repetition"
}
} else {
val extractor2 = Siblings.unapply(pattern)
if (extractor2.nonEmpty) {
"Julie''s siblings are all the same sex"
} else {
"Julie has no siblings"
}
Código de aspecto feo, incluso sin optimizar para obtener solo extractor12
si extractor11
no está vacío, y sin la repetición de código que debería haber ido donde hay un comentario. Así que lo escribiré en otro estilo:
val pattern = "Julie"
& unapply pattern filter (_.isInstanceOf[Tuple2]) flatMap { pattern1 =>
Brothers unapply pattern1._1 flatMap { _ =>
Sisters unapply pattern1._2 flatMap { _ =>
"Julie has both brother(s) and sister(s)"
}
}
} getOrElse {
Siblings unapply pattern map { _ =>
"Julie''s siblings are all the same sex"
} getOrElse {
"Julie has no siblings"
}
}
El patrón de flatMap
/ map
al principio sugiere otra forma de escribir esto:
val pattern = "Julie"
(
for {
pattern1 <- & unapply pattern
if pattern1.isInstanceOf[Tuple2]
_ <- Brothers unapply pattern1._1
_ <- Sisters unapply pattern1._2
} yield "Julie has both brother(s) and sister(s)
) getOrElse (
for {
_ <- Siblings unapply pattern
} yield "Julie''s siblings are all the same sex"
) getOrElse (
"julie has no siblings"
)
Debes poder ejecutar todo este código y ver los resultados por ti mismo.