Scala Language
Соответствие шаблону
Поиск…
Синтаксис
- селекторный матч partialFunction
- селекторное совпадение {список альтернатив случая) // Это наиболее распространенная форма вышеперечисленного
параметры
параметр | подробности |
---|---|
селектор | Выражение, значение которого сопоставляется с образцом. |
альтернативы | список альтернатив, ограниченных case . |
Простой шаблонный матч
В этом примере показано, как сопоставить ввод с несколькими значениями:
def f(x: Int): String = x match {
case 1 => "One"
case 2 => "Two"
case _ => "Unknown!"
}
f(2) // "Two"
f(3) // "Unknown!"
Примечание: _
является случаем падения или по умолчанию , но это не требуется.
def g(x: Int): String = x match {
case 1 => "One"
case 2 => "Two"
}
g(1) // "One"
g(3) // throws a MatchError
Чтобы избежать исключения, это лучшая практика функционального программирования здесь для обработки случая по умолчанию ( case _ => <do something>
). Обратите внимание, что сопоставление по классу case может помочь компилятору выдать предупреждение, если отсутствует случай. То же самое относится к пользовательским типам, которые расширяют запечатанный признак. Если совпадение является общим, то случай по умолчанию может не понадобиться
Также можно сопоставлять значения, не определенные в строке. Они должны быть стабильными идентификаторами , которые получаются либо с использованием имени с заглавной буквыми, либо с использованием обратных ссылок.
С One
и two
определенными где-то еще или переданными как параметры функции:
val One: Int = 1
val two: Int = 2
Их можно сопоставить следующим образом:
def g(x: Int): String = x match {
case One => "One"
case `two` => "Two"
}
В отличие от других языков программирования, поскольку Java, например, не проходит. Если блок-блок соответствует входу, он выполняется, и совпадение завершено. Поэтому наименее конкретный случай должен быть последним блоком случая.
def f(x: Int): String = x match {
case _ => "Default"
case 1 => "One"
}
f(5) // "Default"
f(1) // "Default"
Сравнение шаблонов со стабильным идентификатором
При стандартном сопоставлении шаблонов используемый идентификатор будет затенять любой идентификатор в охватывающей области. Иногда необходимо сопоставлять переменную охватывающей области.
Следующая примерная функция принимает символ и список кортежей и возвращает новый список кортежей. Если символ существовал как первый элемент в одном из кортежей, второй элемент увеличивается. Если он еще не существует в списке, создается новый кортеж.
def tabulate(char: Char, tab: List[(Char, Int)]): List[(Char, Int)] = tab match {
case Nil => List((char, 1))
case (`char`, count) :: tail => (char, count + 1) :: tail
case head :: tail => head :: tabulate(char, tail)
}
Вышеприведенное демонстрирует соответствие шаблону, в котором вход метода, char
, поддерживается «стабильным» в совпадении шаблонов: то есть, если вы вызываете tabulate('x', ...)
, первый оператор case будет интерпретироваться как:
case('x', count) => ...
Scala интерпретирует любую переменную, демаркированную с отметкой галочки как стабильный идентификатор: она также будет интерпретировать любую переменную, начинающуюся с заглавной буквы таким же образом.
Согласование шаблонов на Seq
Чтобы проверить точное количество элементов в коллекции
def f(ints: Seq[Int]): String = ints match {
case Seq() =>
"The Seq is empty !"
case Seq(first) =>
s"The seq has exactly one element : $first"
case Seq(first, second) =>
s"The seq has exactly two elements : $first, $second"
case s @ Seq(_, _, _) =>
s"s is a Seq of length three and looks like ${s}" // Note individual elements are not bound to their own names.
case s: Seq[Int] if s.length == 4 =>
s"s is a Seq of Ints of exactly length 4" // Again, individual elements are not bound to their own names.
case _ =>
"No match was found!"
}
Для извлечения первого элемента (ов) и сохранения остатка в виде коллекции:
def f(ints: Seq[Int]): String = ints match {
case Seq(first, second, tail @ _*) =>
s"The seq has at least two elements : $first, $second. The rest of the Seq is $tail"
case Seq(first, tail @ _*) =>
s"The seq has at least one element : $first. The rest of the Seq is $tail"
// alternative syntax
// here of course this one will never match since it checks
// for the same thing as the one above
case first +: tail =>
s"The seq has at least one element : $first. The rest of the Seq is $tail"
case _ =>
"The seq didn't match any of the above, so it must be empty"
}
В общем, любая форма, которая может быть использована для построения последовательности, может использоваться для сопоставления шаблонов с существующей последовательностью.
Обратите внимание: при использовании Nil
и ::
будет работать, если шаблон соответствует последовательности, он преобразует его в List
и может иметь неожиданные результаты. Ограничьте себя Seq( ...)
и +:
чтобы избежать этого.
Обратите внимание, что при использовании ::
не будет работать для WrappedArray
, Vector
т. Д., См.
scala> def f(ints:Seq[Int]) = ints match {
| case h :: t => h
| case _ => "No match"
| }
f: (ints: Seq[Int])Any
scala> f(Array(1,2))
res0: Any = No match
И с +:
scala> def g(ints:Seq[Int]) = ints match {
| case h+:t => h
| case _ => "No match"
| }
g: (ints: Seq[Int])Any
scala> g(Array(1,2).toSeq)
res4: Any = 1
Охранники (если выражения)
Операторы case можно комбинировать с выражениями, чтобы обеспечить дополнительную логику при сопоставлении шаблонов.
def checkSign(x: Int): String = {
x match {
case a if a < 0 => s"$a is a negative number"
case b if b > 0 => s"$b is a positive number"
case c => s"$c neither positive nor negative"
}
}
Важно, чтобы ваши охранники не создавали не исчерпывающее соответствие (компилятор часто этого не поймает):
def f(x: Option[Int]) = x match {
case Some(i) if i % 2 == 0 => doSomething(i)
case None => doSomethingIfNone
}
Это выдает MatchError
на нечетные числа. Вы должны либо учитывать все случаи, либо использовать случай соответствия шаблону:
def f(x: Option[Int]) = x match {
case Some(i) if i % 2 == 0 => doSomething(i)
case _ => doSomethingIfNoneOrOdd
}
Сравнение шаблонов с классами case
Каждый класс case определяет экстрактор, который может использоваться для захвата членов класса case при сопоставлении с образцом:
case class Student(name: String, email: String)
def matchStudent1(student: Student): String = student match {
case Student(name, email) => s"$name has the following email: $email" // extract name and email
}
Все нормальные правила сопоставления шаблонов применяются - вы можете использовать защитные и постоянные выражения для управления соответствием:
def matchStudent2(student: Student): String = student match {
case Student("Paul", _) => "Matched Paul" // Only match students named Paul, ignore email
case Student(name, _) if name == "Paul" => "Matched Paul" // Use a guard to match students named Paul, ignore email
case s if s.name == "Paul" => "Matched Paul" // Don't use extractor; use a guard to match students named Paul, ignore email
case Student("Joe", email) => s"Joe has email $email" // Match students named Joe, capture their email
case Student(name, email) if name == "Joe" => s"Joe has email $email" // use a guard to match students named Joe, capture their email
case Student(name, email) => s"$name has email $email." // Match all students, capture name and email
}
Соответствие по выбору
Если вы соответствуете по типу Option :
def f(x: Option[Int]) = x match {
case Some(i) => doSomething(i)
case None => doSomethingIfNone
}
Это функционально эквивалентно использованию fold
или map
/ getOrElse
:
def g(x: Option[Int]) = x.fold(doSomethingIfNone)(doSomething)
def h(x: Option[Int]) = x.map(doSomething).getOrElse(doSomethingIfNone)
Шаблон соответствия закрытых черт
Когда шаблон соответствует объекту, тип которого является запечатанным признаком, Scala проверяет во время компиляции, что все случаи «исчерпывающе согласованы»:
sealed trait Shape
case class Square(height: Int, width: Int) extends Shape
case class Circle(radius: Int) extends Shape
case object Point extends Shape
def matchShape(shape: Shape): String = shape match {
case Square(height, width) => "It's a square"
case Circle(radius) => "It's a circle"
//no case for Point because it would cause a compiler warning.
}
Если новый case class
для Shape
позже добавил, все match
заявления на Shape
начнут бросать предупреждение компилятора. Это упростит рефакторинг: компилятор предупредит разработчика обо всех кодах, которые необходимо обновить.
Совпадение шаблона с регулярным выражением
val emailRegex: Regex = "(.+)@(.+)\\.(.+)".r
"[email protected]" match {
case emailRegex(userName, domain, topDomain) => println(s"Hi $userName from $domain")
case _ => println(s"This is not a valid email.")
}
В этом примере регулярное выражение пытается сопоставить указанный адрес электронной почты. Если это произойдет, userName
и domain
извлекаются и печатаются. topDomain
также извлекается, но с этим в этом примере ничего не делается. Вызов .r
на String str
эквивалентен new Regex(str)
. Функция r
доступна через неявное преобразование .
Образец связующего (@)
Значок @
связывает переменную с именем во время совпадения шаблона. Связанная переменная может быть либо полным совпадающим объектом, либо частью согласованного объекта:
sealed trait Shape
case class Rectangle(height: Int, width: Int) extends Shape
case class Circle(radius: Int) extends Shape
case object Point extends Shape
(Circle(5): Shape) match {
case Rectangle(h, w) => s"rectangle, $h x $w."
case Circle(r) if r > 9 => s"large circle"
case c @ Circle(_) => s"small circle: ${c.radius}" // Whole matched object is bound to c
case Point => "point"
}
> res0: String = small circle: 5
Связанный идентификатор может использоваться в условных фильтрах. Таким образом:
case Circle(r) if r > 9 => s"large circle"
может быть записано как:
case c @ Circle(_) if c.radius > 9 => s"large circle"
Имя может быть привязано только к части совпадающего шаблона:
Seq(Some(1), Some(2), None) match {
// Only the first element of the matched sequence is bound to the name 'c'
case Seq(c @ Some(1), _*) => head
case _ => None
}
> res0: Option[Int] = Some(1)
Типы соответствия шаблонов
Сравнение шаблонов также можно использовать для проверки типа экземпляра, а не для использования isInstanceOf[B]
:
val anyRef: AnyRef = ""
anyRef match {
case _: Number => "It is a number"
case _: String => "It is a string"
case _: CharSequence => "It is a char sequence"
}
//> res0: String = It is a string
Порядок дел важен:
anyRef match {
case _: Number => "It is a number"
case _: CharSequence => "It is a char sequence"
case _: String => "It is a string"
}
//> res1: String = It is a char sequence
Таким образом, он похож на классический оператор «switch» без пропущенных функций. Однако вы также можете сопоставлять совпадение и «извлекать» значения из соответствующего типа. Например:
case class Foo(s: String)
case class Bar(s: String)
case class Woo(s: String, i: Int)
def matcher(g: Any):String = {
g match {
case Bar(s) => s + " is classy!"
case Foo(_) => "Someone is wicked smart!"
case Woo(s, _) => s + " is adventerous!"
case _ => "What are we talking about?"
}
}
print(matcher(Foo("Diana"))) // prints 'Diana is classy!'
print(matcher(Bar("Hadas"))) // prints 'Someone is wicked smart!'
print(matcher(Woo("Beth", 27))) // prints 'Beth is adventerous!'
print(matcher(Option("Katie"))) // prints 'What are we talking about?'
Обратите внимание, что в случае Foo
и Woo
мы используем знак подчеркивания ( _
) для «соответствия несвязанной переменной». То есть значение (в данном случае Hadas
и 27
соответственно) не связано с именем и, следовательно, недоступно в обработчике для этого случая. Это полезно сокращать, чтобы соответствовать «любому» значению, не беспокоясь о том, что это за значение.
Совпадение с образцом, скомпилированное как tablewitch или lookupswitch
@switch
аннотации сообщают компилятор , что match
заявление может быть заменено одной tableswitch
инструкции на уровне байт - коды. Это небольшая оптимизация, которая позволяет удалить ненужные сравнения и переменные нагрузки во время выполнения.
Аннотации @switch
работают только для совпадений с литеральными константами и final val
идентификаторами final val
. Если совпадение шаблона невозможно скомпилировать в качестве tableswitch
/ lookupswitch
, компилятор поднимет предупреждение.
import annotation.switch
def suffix(i: Int) = (i: @switch) match {
case 1 => "st"
case 2 => "nd"
case 3 => "rd"
case _ => "th"
}
Результаты совпадают с результатами, полученными с обычным шаблоном:
scala> suffix(2)
res1: String = "2nd"
scala> suffix(4)
res2: String = "4th"
Из документа Scala (2.8+) - @switch
:
Аннотацию, которая должна применяться к выражению соответствия. Если присутствует, компилятор будет проверять, что совпадение было скомпилировано для переключателя tableswitch или lookupswitch, и выдает ошибку, если вместо этого компилируется в серию условных выражений.
Из спецификации Java:
- tableswitch : «Таблица перехода к индексу и прыжок»
- lookupswitch : «Доступ к таблице перехода по ключевому совпадению и прыжку»
Соответствие нескольких шаблонов сразу
|
может использоваться для совпадения одного аргумента case с несколькими входами, чтобы получить тот же результат:
def f(str: String): String = str match {
case "foo" | "bar" => "Matched!"
case _ => "No match."
}
f("foo") // res0: String = Matched!
f("bar") // res1: String = Matched!
f("fubar") // res2: String = No match.
Обратите внимание, что при совпадении значений этот способ работает хорошо, следующие соответствия типов вызовут проблемы:
sealed class FooBar
case class Foo(s: String) extends FooBar
case class Bar(s: String) extends FooBar
val d = Foo("Diana")
val h = Bar("Hadas")
// This matcher WILL NOT work.
def matcher(g: FooBar):String = {
g match {
case Foo(s) | Bar(s) => print(s) // Won't work: s cannot be resolved
case Foo(_) | Bar(_) => _ // Won't work: _ is an unbound placeholder
case _ => "Could not match"
}
}
Если в последнем случае (с _
) вам не нужно значение несвязанной переменной и просто хотите сделать что-то еще, вы в порядке:
def matcher(g: FooBar):String = {
g match {
case Foo(_) | Bar(_) => "Is either Foo or Bar." // Works fine
case _ => "Could not match"
}
}
В противном случае вы останетесь с разбивкой своих дел:
def matcher(g: FooBar):String = {
g match {
case Foo(s) => s
case Bar(s) => s
case _ => "Could not match"
}
}
Выравнивание шаблонов на кортежах
Учитывая следующий List
кортежей:
val pastries = List(("Chocolate Cupcake", 2.50),
("Vanilla Cupcake", 2.25),
("Plain Muffin", 3.25))
Согласование шаблонов может использоваться для обработки каждого элемента по-разному:
pastries foreach { pastry => pastry match { case ("Plain Muffin", price) => println(s"Buying muffin for $price") case p if p._1 contains "Cupcake" => println(s"Buying cupcake for ${p._2}") case _ => println("We don't sell that pastry") } }
В первом случае показано, как сопоставлять конкретную строку и получать соответствующую цену. Второй случай показывает использование if и tuple extract для сопоставления с элементами кортежа.