Поиск…


Синтаксис

  • селекторный матч 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 для сопоставления с элементами кортежа.



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow