Correspondance de modèles vs si-autre

Je suis novice à Scala. Récemment, j’écrivais une application de loisir et je me suis surpris à essayer d’utiliser la correspondance de motif au lieu de sinon, dans de nombreux cas.

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

au lieu de

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

Ces approches sont-elles identiques? L’un d’entre eux est-il plus préférable qu’un autre pour une raison quelconque?

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

Je ne sais pas pourquoi vous souhaitez utiliser la deuxième version, plus longue et plus compliquée.

 scala> :javap -cp MatchVsIf Comstackd from "" 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."":(Ljava/lang/Object;)V 29: athrow 

Et c’est beaucoup plus le bytecode du match. C’est quand même assez efficace (il n’y a pas de boxe à moins que le match ne génère une erreur, ce qui ne peut pas arriver ici), mais pour des raisons de compacité et de performances, il faut privilégier if / else . Si la clarté de votre code est grandement améliorée en utilisant la correspondance, allez-y (sauf dans les rares cas où vous savez que les performances sont critiques, et vous voudrez peut-être comparer la différence).

Ne correspond pas à un seul booléen; utiliser un if-else.

Incidemment, le code est mieux écrit sans dupliquer println .

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

Une manière sans doute meilleure serait de créer une correspondance directe sur la chaîne, et non sur le résultat de la comparaison, car cela évite la “cécité booléenne”. http://existentialtype.wordpress.com/2011/03/15/boolean-blindness/

L’inconvénient est la nécessité d’utiliser des guillemets pour protéger la variable EnterPassword contre l’observation.

En gros, vous devriez éviter de traiter les booléens autant que possible, car ils ne transmettent aucune information au niveau du type.

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

Les deux déclarations sont équivalentes en termes de sémantique de code. Mais il est possible que le compilateur crée du code plus compliqué (et donc inefficace) dans un cas (la match ).

La correspondance de motifs est généralement utilisée pour séparer des constructions plus complexes, telles que les expressions polymorphes ou pour déconstruire (ne pas unapply ) des objects dans leurs composants. Je ne conseillerais pas de l’utiliser comme substitut pour une simple déclaration if-else – il n’y a rien de mal à if-else .

Notez que vous pouvez l’utiliser comme une expression dans Scala. Ainsi, vous pouvez écrire

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

Je m’excuse pour cet exemple stupide.

Pour la grande majorité du code qui n’est pas sensible aux performances, il y a de nombreuses raisons pour lesquelles vous souhaitez utiliser la correspondance de modèle si / else:

  • il applique une valeur de retour commune et tape pour chacune de vos twigs
  • dans les langages avec des contrôles d’exhaustivité (comme Scala), cela vous oblige à considérer explicitement tous les cas (et à éviter ceux dont vous n’avez pas besoin)
  • cela empêche les retours précoces, qui deviennent de plus en plus difficiles à raisonner en cascade, augmente ou les twigs deviennent plus longues que la hauteur de votre écran (elles deviennent alors invisibles). Avoir un niveau supplémentaire d’indentation vous avertira que vous êtes dans une scope.
  • cela peut vous aider à identifier la logique à retirer. Dans ce cas, le code aurait pu être réécrit et rendu plus DRY, débogeable et testable comme ceci:
 val errorMessage = user.password == enteredPassword match { case true => "User is authenticated" case false => "Entered password is invalid" } println(errorMesssage) 

Voici une implémentation de bloc if / else équivalente:

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

Oui, vous pouvez prétendre que pour quelque chose d’aussi simple qu’une vérification booléenne, vous pouvez utiliser une expression if. Mais ce n’est pas pertinent ici et ne s’adapte pas bien aux conditions avec plus de 2 twigs.

Si votre souci supérieur est la maintenabilité ou la lisibilité, la correspondance de modèle est géniale et vous devriez l’utiliser même pour des choses mineures!

Je suis tombé sur la même question et j’ai passé des tests écrits:

  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: Long = 22 timeMatch: Long = 1092

Dans mon environnement (scala 2.12 et java 8), les résultats sont différents. La correspondance est toujours meilleure dans le code ci-dessus:

timeIf: Long = 249 timeMatch: Long = 68

Je suis ici pour donner un avis différent: pour l’exemple spécifique que vous proposez, le second (si … sinon …) le style est en fait meilleur car il est beaucoup plus facile à lire.

En fait, si vous placez votre premier exemple dans IntelliJ, il vous proposera de changer de style (si … sinon …). Voici la suggestion de style IntelliJ:

 Trivial match can be simplified less... (⌘F1) Suggests to replace sortingvial pattern match on a boolean expression with a conditional statement. Before: bool match { case true => ??? case false => ??? } After: if (bool) { ??? } else { ??? }