2012-02-13 54 views
53

我是Scala的新手。最近我正在写一个业余爱好程序,并且在很多情况下发现自己试图使用模式匹配而不是if-else。模式匹配vs if-else

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

代替

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

是这些办法平等的吗?出于某种原因,其中一人比另一人更可取吗?

回答

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

我不确定你为什么要使用更长和更笨重的第二个版本。

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 

而这也是比赛的更多字节码。这是相当即使如此(除非匹配抛出一个错误,这不会发生在这里没有拳击),但在紧凑性和性能方面,应该倾向于使用if/else。但是,如果通过使用匹配来大大提高代码的清晰度,请继续(除非您知道性能至关重要,然后您可能想要比较差异的罕见情况除外)。

+1

我只是在模式匹配的印象下。我想这就是为什么我试图在任何地方使用它:)谢谢,我会遵循你的建议。 – Soteric 2012-02-13 20:12:53

+6

@Soteric这是Scala程序员的共同阶段。你会经历其他更糟糕的阶段。 :-) – 2012-02-13 20:41:01

+0

@Daniel就像有跨越几行的类型签名? – ziggystar 2012-02-13 22:34:16

9

这两个语句在代码语义方面是等价的。但是在一种情况下,编译器可能会创建更复杂(因此效率低下)的代码(match)。

模式匹配通常用于分解更复杂的构造,如多态表达式或将对象解构为其组件。我不会建议使用它作为简单的if-else声明的替代品 - if-else没有任何问题。

请注意,您可以将它用作Scala中的表达式。因此你可以写

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

我为这个愚蠢的例子道歉。

23

不要在单个布尔模式上模式匹配;使用if-else。

顺便提一下,代码写得更好,不需要重复println

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

D'oh。这应该是我的榜样。 – ziggystar 2012-02-13 20:35:43

11

可以说是一个更好的办法是模式匹配的字符串直接,而不是在比较的结果,因为它避免了“布尔失明”。 http://existentialtype.wordpress.com/2011/03/15/boolean-blindness/

一个缺点是需要使用反引号来保护输入的密码变量不被遮挡。

基本上,您应该尽量避免处理布尔值,因为他们不会在类型级别传达任何信息。

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

心中已经遇到了同样的问题,并有笔试:

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

2

对于大部分的代码,ISN对性能非常敏感,为什么要使用if/else的模式匹配有很多很好的理由:

  • 它强制为每个分支的共同返回值和类型
  • 与全面性检查(比如Scala),它强迫你明确地考虑所有情况(和NOOP你不需要的)
  • 它语言
  • 阻止早期回报,如果它们级联,数量增加,或者分支长出的时间比屏幕的高度更长(在这一点上它们变得不可见),那么它们会变得更难以推理。有一个额外的缩进级别会警告你你在一个范围内。
  • 它可以帮助您确定需要拔出的逻辑。在这种情况下,代码可能已被改写,变得更加干燥,可调试和可测试如下:
val errorMessage = user.password == enteredPassword match { 
    case true => "User is authenticated" 
    case false => "Entered password is invalid" 
} 

println(errorMesssage) 

下面是一个等价的if/else块执行:

var errorMessage = "" 

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

println(errorMessage) 

是的,你可以认为对于像布尔检查这样简单的事情,您可以使用if-expression。但这与此无关,并且不适用于拥有两个以上分支的条件。

如果您的高度关注点是可维护性或可读性,模式匹配非常棒,您应该将它用于小事!

+2

使用if/else不需要突变。三元运营商的斯卡拉相当于将解决这个问题: 'VAL的errorMessage = 如果(user.password的== enteredPassword) “用户进行身份验证” 其他 “输入的密码无效”' – 2016-08-11 08:22:52

+0

我解决了这个我原来的评论:“是的,你可以争辩说,对于像布尔检查这样简单的事情,你可以使用if-expression,但这与这里不相关,并且不能很好地适应具有两个以上分支的条件。 – 2017-01-01 00:44:58

+0

你写道:“尽管使用if/else编写此代码需要进行突变”。这仍然不正确。只要所有分支都是相同类型的,就不需要if/else的突变。例如:'val k = if(false)“1”else if(false)“2”else“3” – 2017-01-02 06:28:11