Соответствие шаблону vs if-else

Я новичок в 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")

Являются ли эти подходы равными? Является ли один из них более предпочтительным, чем другой, почему-то?

Ответ 1

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

Я не уверен, почему вы хотите использовать более длинную и 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

И это намного больше байт-кода для матча. Он довольно эффективен даже в этом случае (там нет бокса, если матч не выдает ошибку, что не может произойти здесь), но для компактности и производительности следует одобрить if/else. Однако, если ясность вашего кода значительно улучшена за счет использования соответствия, за исключением тех редких случаев, когда вы знаете, что производительность критическая, и тогда вам может понадобиться сравнить разницу).

Ответ 2

Не совпадать с шаблоном на одном булевом; используйте if-else.

Кстати, код лучше написан без дублирования println.

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

Ответ 3

Возможно, лучшим способом было бы совпадение шаблонов в строке напрямую, а не результат сравнения, поскольку оно позволяет избежать "булевой слепоты". http://existentialtype.wordpress.com/2011/03/15/boolean-blindness/

Один недостаток - необходимость использования backquotes для защиты введенной переменнойPassword от затенения.

В принципе, вам следует избегать как можно большего числа операций с булевыми элементами, поскольку они не передают информацию на уровне типа.

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

Ответ 4

Оба оператора эквивалентны в терминах семантики кода. Но возможно, что компилятор создает более сложный (и, следовательно, неэффективный) код в одном случае (match).

Совпадение шаблонов обычно используется для разлома более сложных конструкций, таких как полиморфные выражения или деконструирования (unapply ing) объектов в их компоненты. Я бы не советовал использовать его в качестве суррогата для простого утверждения if-else - нет ничего плохого в if-else.

Обратите внимание, что вы можете использовать его как выражение в Scala. Таким образом, вы можете написать

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

Извиняюсь за глупый пример.

Ответ 5

Я столкнулся с тем же вопросом и написал тесты:

     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

Для большей части кода, который не чувствителен к производительности, существует множество веских причин, по которым вы хотите использовать сопоставление шаблонов над 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-выражение. Но это не имеет значения здесь и недостаточно хорошо подходит для условий с более чем двумя ветвями.

Если ваша повышенная озабоченность связана с ремонтопригодностью или удобочитаемостью, сопоставление образцов является удивительным, и вы должны использовать его даже для незначительных вещей!