2012-02-13 16 views
53

Soy un novato en Scala. Hace poco escribí una aplicación de hobby y me puse a prueba tratando de usar patrones de coincidencia en lugar de if-else en muchos casos.Coincidencia de patrones vs if-else

user.password == enteredPassword match { 
    case true => println("User is authenticated") 
    case false => println("Entered password is invalid") 
} 

en lugar de

if(user.password == enteredPassword) 
    println("User is authenticated") 
else 
    println("Entered password is invalid") 

Son estos enfoques son iguales? ¿Es uno de ellos más preferible que otro por alguna razón?

Respuesta

68
class MatchVsIf { 
    def i(b: Boolean) = if (b) 5 else 4 
    def m(b: Boolean) = b match { case true => 5; case false => 4 } 
} 

No estoy seguro de por qué querría utilizar la segunda versión, más larga y clunkier.

scala> :javap -cp MatchVsIf 
Compiled from "<console>" 
public class MatchVsIf extends java.lang.Object implements scala.ScalaObject{ 
public int i(boolean); 
    Code: 
    0: iload_1 
    1: ifeq 8 
    4: iconst_5 
    5: goto 9 
    8: iconst_4 
    9: ireturn 

public int m(boolean); 
    Code: 
    0: iload_1 
    1: istore_2 
    2: iload_2 
    3: iconst_1 
    4: if_icmpne 11 
    7: iconst_5 
    8: goto 17 
    11: iload_2 
    12: iconst_0 
    13: if_icmpne 18 
    16: iconst_4 
    17: ireturn 
    18: new #14; //class scala/MatchError 
    21: dup 
    22: iload_2 
    23: invokestatic #20; //Method scala/runtime/BoxesRunTime.boxToBoolean:(Z)Ljava/lang/Boolean; 
    26: invokespecial #24; //Method scala/MatchError."<init>":(Ljava/lang/Object;)V 
    29: athrow 

Y eso es mucho más bytecode para el partido también. Es bastante incluso eficiente (no hay boxeo a menos que el partido arroje un error, que no puede suceder aquí), pero para compacidad y rendimiento uno debe favorecer if/else. Sin embargo, si mejora la claridad de su código mediante el uso de coincidencias, continúe (excepto en aquellos casos excepcionales en los que sabe que el rendimiento es crítico, y luego es posible que desee comparar la diferencia).

+1

Estoy bajo impresión de coincidencia de patrón. Creo que es por eso que estoy tratando de usarlo en todas partes :) Gracias, seguiré tu consejo. – Soteric

+6

@Soteric Esa es una fase común para los programadores de Scala. Pasarás por otras fases peores. :-) –

+0

@Daniel ¿Te gusta tener firmas de tipo que abarcan varias líneas? – ziggystar

9

Ambas declaraciones son equivalentes en términos de semántica de código. Pero es posible que el compilador cree un código más complicado (y, por lo tanto, ineficiente) en un caso (el match).

La coincidencia de patrones se utiliza generalmente para separar constructos más complicados, como expresiones polimórficas o objetos deconstruidos (unapply ing) en sus componentes. No aconsejaría utilizarlo como sustituto de una simple declaración if-else; no hay nada de malo en if-else.

Tenga en cuenta que puede usarlo como una expresión en Scala. De este modo se puede escribir

val foo = if(bar.isEmpty) foobar else bar.foo 

Me disculpo por el ejemplo estúpida.

23

No coincide el patrón en un solo booleano; usa un if-else.

Por cierto, el código está mejor escrito sin duplicar println.

println(
    if(user.password == enteredPassword) 
    "User is authenticated" 
    else 
    "Entered password is invalid" 
) 
+0

D'oh. Ese debería haber sido mi ejemplo. – ziggystar

11

Una forma podría decirse que sería mejor para el ajuste de patrones en la cadena directamente, no en el resultado de la comparación, ya que evita la "ceguera booleano". http://existentialtype.wordpress.com/2011/03/15/boolean-blindness/

Un inconveniente es la necesidad de utilizar comillas inversas para proteger la variable de contraseña introducida de la sombra.

Básicamente, debería evitar el tratar con booleanos tanto como sea posible, ya que no transmiten ninguna información en el nivel de tipo.

user.password match { 
    case `enteredPassword` => Right(user) 
    case _ => Left("passwords don't match") 
} 
2

Me'v se encontró con la misma pregunta, y tenía pruebas escritas:

 def factorial(x: Int): Int = { 
     def loop(acc: Int, c: Int): Int = { 
      c match { 
      case 0 => acc 
      case _ => loop(acc * c, c - 1) 
      } 
     } 
     loop(1, x) 
     } 

     def factorialIf(x: Int): Int = { 
     def loop(acc: Int, c: Int): Int = 
      if (c == 0) acc else loop(acc * c, c - 1) 
     loop(1, x) 
     } 

    def measure(e: (Int) => Int, arg:Int, numIters: Int): Long = { 
     def loop(max: Int): Unit = { 
      if (max == 0) 
      return 
      else { 
      val x = e(arg) 
      loop(max-1) 
      } 
     } 

     val startMatch = System.currentTimeMillis() 
     loop(numIters) 
     System.currentTimeMillis() - startMatch 
     }     
val timeIf = measure(factorialIf, 1000,1000000) 
val timeMatch = measure(factorial, 1000,1000000) 

timeIf: Largo = 22 timeMatch: Largo = 1092

2

Para la gran mayoría de código que ISN No es sensible al rendimiento, hay muchas razones por las que desea utilizar la coincidencia de patrones en if/else:

  • se impone un valor de retorno común y tipo para cada una de sus ramas
  • en idiomas con los controles de exhaustividad (como Scala), obliga a considerar de forma explícita todos los casos (y Noop los que no es necesario)
  • se previene las devoluciones anticipadas, que se vuelven más difíciles de razonar si se multiplican en cascada, o las ramas crecen más que la altura de la pantalla (momento en el que se vuelven invisibles). Tener un nivel extra de sangría te avisará que estás dentro de un alcance.
  • puede ayudarle a identificar la lógica para extraer. En este caso, el código podría haber sido reescrito y hecho más SECO, depurable y comprobable como esto:
val errorMessage = user.password == enteredPassword match { 
    case true => "User is authenticated" 
    case false => "Entered password is invalid" 
} 

println(errorMesssage) 

Aquí hay un equivalente if/else aplicación de bloque:

var errorMessage = "" 

if(user.password == enteredPassword) 
    errorMessage = "User is authenticated" 
else 
    errorMessage = "Entered password is invalid" 

println(errorMessage) 

Sí, se puede argumentan que para algo tan simple como una verificación booleana puede usar una expresión if. Pero eso no es relevante aquí y no se adapta bien a las condiciones con más de 2 ramas.

Si su mayor preocupación es el mantenimiento o la legibilidad, la coincidencia de patrones es increíble y debe usarlo incluso para cosas menores.

+2

Usar if/else no requiere mutación. El equivalente del operador ternario en Scala resolvería esto: 'val = errorMessage si (== user.password enteredPassword) "usuario se autentica" demás "contraseña introducida no es válida"' –

+0

me abordó esta en mi Comentario original: "Sí, puede argumentar que para algo tan simple como un control booleano puede usar una expresión if. Pero eso no es relevante aquí y no se adapta bien a las condiciones con más de 2 ramas". –

+0

Usted escribió "Mientras que escribir esto usando if/else habría requerido mutación". Eso sigue siendo incorrecto No necesita mutación para if/else, siempre que todas las ramas sean del mismo tipo.Ej: 'val k = if (false)" 1 "else if (false)" 2 "else" 3 "' –

Cuestiones relacionadas