模式匹配vs if-else

时间:2012-02-13 19:36:54

标签: scala

我是Scala的新手。最近我写了一个爱好应用程序,并发现自己试图在很多情况下使用模式匹配而不是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")

这些方法是否相同?出于某种原因,其中一个比另一个更受欢迎吗?

9 个答案:

答案 0 :(得分:88)

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 :(得分:28)

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

顺便说一句,代码编写得更好而不会重复println

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

答案 2 :(得分:14)

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

一个缺点是需要使用反引号来保护enteredPassword变量不被遮蔽。

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

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

答案 3 :(得分:11)

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

模式匹配通常用于分解更复杂的构造,如多态表达式或解构(unapply)对象到其组件中。我不建议将它用作简单的 if-else 语句的代理 - if-else 没有任何问题。

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

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

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

答案 4 :(得分:5)

对于大多数对性能不敏感的代码,有很多很好的理由说明为什么你想要使用if / else进行模式匹配:

  • 它为每个分支强制执行公共返回值和类型
  • 在使用详尽检查的语言(如Scala)中,它会强制您明确考虑所有情况(并且不需要您不需要的情况)
  • 它可以防止早期返回,如果它们级联,数量增长,或者树枝长度超过屏幕高度(此时它们变得不可见),就会变得难以推理。额外级别的缩进将警告您,您已进入范围。
  • 它可以帮助您识别要拔出的逻辑。在这种情况下,代码可能已被重写,并且更加干燥,可调试和可测试,如下所示:
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个以上分支的条件。

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

答案 5 :(得分:3)

我遇到了同样的问题,并写了测试:

     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

答案 6 :(得分:2)

到2020年,Scala编译器在模式匹配的情况下生成效率更高的字节码。被接受的答案中的绩效评论在2020年具有误导性。

模式匹配生成的字节码给if-else带来了激烈的竞争,有时模式匹配胜出,从而带来更好,更一致的结果。

根据情况和简单性,可以使用模式匹配或if-else。 但是模式匹配性能差的结论不再有效。

您可以尝试以下代码片段并查看结果:

def testMatch(password: String, enteredPassword: String) = {
    val entering = System.nanoTime()
    password == enteredPassword match {
      case true => {
        println(s"User is authenticated. Time taken to evaluate True in match : ${System.nanoTime() - entering}"
        )
      }
      case false => {
        println(s"Entered password is invalid. Time taken to evaluate false in match : ${System.nanoTime() - entering}"
        )
      }
    }
  }


 testMatch("abc", "abc")
 testMatch("abc", "def")
    
Pattern Match Results : 
User is authenticated. Time taken to evaluate True in match : 1798
Entered password is invalid. Time taken to evaluate false in match : 3878


If else :

def testIf(password: String, enteredPassword: String) = {
    val entering = System.nanoTime()
    if (password == enteredPassword) {
      println(
        s"User is authenticated. Time taken to evaluate if : ${System.nanoTime() - entering}"
      )
    } else {
      println(
        s"Entered password is invalid.Time taken to evaluate else ${System.nanoTime() - entering}"
      )
    }
  }

testIf("abc", "abc")
testIf("abc", "def")

If-else time results:
User is authenticated. Time taken to evaluate if : 65062652
Entered password is invalid.Time taken to evaluate else : 1809

PS:由于数字的精确度为纳米,因此结果可能无法与确切的数字精确匹配,但有关性能的论点仍然很好。

答案 7 :(得分:1)

我在这里提出不同的意见: 对于您提供的特定示例,第二种(如果... else ...)样式实际上更好,因为它更易于阅读。

实际上,如果您将第一个示例放入IntelliJ中,它将建议您更改为第二个(如果... else ...)样式。这是IntelliJ风格的建议:

<?php
class Autocomplete extends CI_Controller{
function __construct() {
    parent::__construct();
    $this->load->model('datacomplete');
}

public function index(){
    //$this->load->view('view_demo');
    $this->load->view('home');
}
public function GetCompanyName(){
    $keyword=$this->input->post('keyword');
    $data=$this->datacomplete->GetRow($keyword);        
    echo json_encode($data);
 }

}
?>

答案 8 :(得分:0)

在我的环境中(scala 2.12和java 8),我得到了不同的结果。匹配在上面的代码中表现更好:

timeIf:Long = 249 timeMatch:长= 68