Сопоставление с образцом против if-else - PullRequest
66 голосов
/ 13 февраля 2012

Я новичок в Скале. Недавно я писал приложение для хобби и поймал себя на том, что во многих случаях пытаюсь использовать сопоставление с образцом вместо 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")

Равны ли эти подходы? По какой-то причине один из них предпочтительнее другого?

Ответы [ 8 ]

90 голосов
/ 14 февраля 2012
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.Однако, если ясность вашего кода значительно улучшена с помощью сопоставления, продолжайте (за исключением тех редких случаев, когда вы знаете, что производительность критична, и тогда вы, возможно, захотите сравнить разницу).

28 голосов
/ 14 февраля 2012

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

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

println(
  if(user.password == enteredPassword) 
    "User is authenticated"
  else 
    "Entered password is invalid"
)
14 голосов
/ 12 октября 2012

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

Одним недостатком является необходимость использования обратных кавычек, чтобы защитить переменную enterPassword от затенения.

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

user.password match {
    case `enteredPassword` => Right(user)
    case _ => Left("passwords don't match")
}
11 голосов
/ 13 февраля 2012

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

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

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

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

Прошу прощения за глупый пример.

5 голосов
/ 04 мая 2016

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

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

3 голосов
/ 23 апреля 2013

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

     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

1 голос
/ 21 июня 2018

Я здесь, чтобы предложить другое мнение: Для конкретного примера, который вы предлагаете, второй (если ... еще ...) стиль на самом деле лучше, потому что его намного легче читать.

Фактически, если вы поместите свой первый пример в IntelliJ, он предложит вам перейти на второй (если ... еще ...) стиль. Вот совет по стилю IntelliJ:

Trivial match can be simplified less... (⌘F1) 

Suggests to replace trivial pattern match on a boolean expression with a conditional statement.
Before:
    bool match {
      case true => ???
      case false => ???
    }
After:
    if (bool) {
      ???
    } else {
      ???
    }
0 голосов
/ 04 июня 2018

В моем окружении (scala 2.12 и java 8) я получаю разные результаты. Соответствие работает лучше в коде выше:

timeIf: Long = 249 timeMatch: Long = 68

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...