switch pattern for scala pattern-matching

pattern - Scala: forma corta de coincidencia de patrones que devuelve Boolean



scala for (6)

El operador de match en Scala es más poderoso cuando se usa en estilo funcional. Esto significa que, en lugar de "hacer algo" en las declaraciones de case , devolvería un valor útil. Aquí hay un ejemplo de un estilo imperativo:

var value:Int = 23 val command:String = ... // we get this from somewhere command match { case "duplicate" => value = value * 2 case "negate" => value = -value case "increment" => value = value + 1 // etc. case _ => // do nothing } println("Result: " + value)

Es muy comprensible que el "no hacer nada" de arriba duela un poco, porque parece superfluo. Sin embargo, esto se debe al hecho de que lo anterior está escrito en estilo imperativo. Si bien construcciones como estas a veces pueden ser necesarias, en muchos casos puede refactorizar su código a un estilo funcional:

val value:Int = 23 val command:String = ... // we get this from somewhere val result:Int = command match { case "duplicate" => value * 2 case "negate" => -value case "increment" => value + 1 // etc. case _ => value } println("Result: " + result)

En este caso, utiliza la sentencia de match completa como un valor que puede, por ejemplo, asignar a una variable. Y también es mucho más obvio que la declaración de match debe devolver un valor en cualquier caso; si faltara el último caso, el compilador no podría inventar algo.

Es una cuestión de gusto, pero algunos desarrolladores consideran que este estilo es más transparente y fácil de manejar en ejemplos más reales. Apuesto a que los inventores del lenguaje de programación Scala tenían un uso más funcional en mente para la match , y de hecho la declaración if tiene más sentido si solo necesitas decidir si se debe o no tomar una determinada acción. (Por otro lado, también puede usarlo de forma funcional, porque también tiene un valor de retorno ...)

Me encontré escribiendo algo así bastante a menudo:

a match { case `b` => // do stuff case _ => // do nothing }

¿Hay alguna manera más corta de verificar si algún valor coincide con un patrón? Quiero decir, en este caso podría escribir if (a == b) // do stuff , pero ¿y si el patrón es más complejo? Como cuando se compara con una lista o cualquier patrón de complejidad arbitraria. Me gustaría poder escribir algo como esto:

if (a matches b) // do stuff

Soy relativamente nuevo en Scala, así que, por favor, si me falta algo grande :)


Esta es exactamente la razón por la que escribí estas funciones, que aparentemente son impresionantemente oscuras ya que nadie las ha mencionado.

scala> import PartialFunction._ import PartialFunction._ scala> cond("abc") { case "def" => true } res0: Boolean = false scala> condOpt("abc") { case x if x.length == 3 => x + x } res1: Option[java.lang.String] = Some(abcabc) scala> condOpt("abc") { case x if x.length == 4 => x + x } res2: Option[java.lang.String] = None


Esto podría ayudar:

class Matches(m: Any) { def matches[R](f: PartialFunction[Any, R]) { if (f.isDefinedAt(m)) f(m) } } implicit def any2matches(m: Any) = new Matches(m) scala> ''c'' matches { case x: Int => println("Int") } scala> 2 matches { case x: Int => println("Int") } Int

Ahora, alguna explicación sobre la naturaleza general del problema.

¿Dónde puede una coincidencia suceden?

Hay tres lugares donde puede haber coincidencia de patrones: val , case y for . Las reglas para ellos son:

// throws an exception if it fails val pattern = value // filters for pattern, but pattern cannot be "identifier: Type", // though that can be replaced by "id1 @ (id2: Type)" for the same effect for (pattern <- object providing map/flatMap/filter/withFilter/foreach) ... // throws an exception if none of the cases match value match { case ... => ... }

Sin embargo, existe otra situación en la case puede aparecer un case , que es función y literales de función parcial. Por ejemplo:

val f: Any => Unit = { case i: Int => println(i) } val pf: PartialFunction[Any, Unit] = { case i: Int => println(i) }

Ambas funciones y funciones parciales lanzarán una excepción si se llama con un argumento que no coincide con ninguna de las declaraciones de casos. Sin embargo, las funciones parciales también proporcionan un método llamado isDefinedAt que puede probar si se puede hacer o no una coincidencia, así como un método llamado lift , que convertirá una función PartialFunction[T, R] en una Function[T, Option[R]] , lo que significa que los valores que no coinciden darán como resultado None en lugar de arrojar una excepción.

¿Qué es un partido?

Una coincidencia es una combinación de muchas pruebas diferentes:

// assign anything to x case x // only accepts values of type X case x: X // only accepts values matches by pattern case x @ pattern // only accepts a value equal to the value X (upper case here makes a difference) case X // only accepts a value equal to the value of x case `x` // only accept a tuple of the same arity case (x, y, ..., z) // only accepts if extractor(value) returns true of Some(Seq()) (some empty sequence) case extractor() // only accepts if extractor(value) returns Some something case extractor(x) // only accepts if extractor(value) returns Some Seq or Tuple of the same arity case extractor(x, y, ..., z) // only accepts if extractor(value) returns Some Tuple2 or Some Seq with arity 2 case x extractor y // accepts if any of the patterns is accepted (patterns may not contain assignable identifiers) case x | y | ... | z

Ahora, los extractores son los métodos unapply o unapplySeq , el primer Boolean u Option[T] vuelve, y la segunda Option[Seq[T]] regresa, donde None significa que no se hace ninguna coincidencia, y Some(result) intentará hacer coincidir el result como se describió anteriormente.

Entonces, aquí hay todo tipo de alternativas sintácticas, que simplemente no son posibles sin el uso de una de las tres construcciones en las que puede haber coincidencias de patrones. Puede emular algunas de las características, como la igualdad de valores y los extractores, pero no todas.


Lo mejor que se me ocurre es esto:

def matches[A](a:A)(f:PartialFunction[A, Unit]) = f.isDefinedAt(a) if (matches(a){case ... =>}) { //do stuff }

Sin embargo, esto no te hará ganar puntos de estilo.


Los patrones también se pueden usar en expresiones. Tu código de muestra

a match { case b => // do stuff case _ => // do nothing }

puede expresarse como

for(b <- Some(a)) //do stuff

El truco es envolver a para convertirlo en un enumerador válido. Eg List (a) también funcionaría, pero creo que Some (a) está más cerca de su significado previsto.


La respuesta de Kim se puede "mejorar" para que se ajuste mejor a sus necesidades:

class AnyWrapper[A](wrapped: A) { def matches(f: PartialFunction[A, Unit]) = f.isDefinedAt(wrapped) } implicit def any2wrapper[A](wrapped: A) = new AnyWrapper(wrapped)

entonces:

val a = "a" :: Nil if (a matches { case "a" :: Nil => }) { println("match") }

No lo haría, sin embargo. La secuencia => }) { es realmente fea aquí, y todo el código parece mucho menos claro que una coincidencia normal. Además, obtienes la sobrecarga en tiempo de compilación de buscar la conversión implícita y la sobrecarga en tiempo de ejecución de envolver la coincidencia en una función PartialFunction (sin contar los conflictos que podrías tener con otros métodos de matches ya definidos, como el de String )

Para verse un poco mejor (y ser menos detallado), puede agregar esta definición a AnyWrapper :

def ifMatch(f: PartialFunction[A, Unit]): Unit = if (f.isDefinedAt(wrapped)) f(wrapped)

y úsalo así:

a ifMatch { case "a" :: Nil => println("match") }

que le ahorra su case _ => línea, pero requiere dobles llaves si desea un bloque en lugar de una sola declaración ... No es tan agradable.

Tenga en cuenta que esta construcción no está realmente en el espíritu de la programación funcional, ya que solo puede utilizarse para ejecutar algo que tiene efectos secundarios. No podemos usarlo fácilmente para devolver un valor (por lo tanto, el valor de retorno de la Unit ), ya que la función es parcial, necesitaríamos un valor predeterminado o podríamos devolver una instancia de la Option . Pero aquí de nuevo, probablemente lo desenvolveríamos con una cerilla, por lo que no ganaríamos nada.

Francamente, es mejor acostumbrarse a ver y usar esos match frecuencia, y alejarse de este tipo de construcciones de estilo imperativo (siguiendo la buena explicación de Madoc ).