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