Поиск…


Синтаксис

  • val extractor (extractValue1, _ / * игнорируется второе извлеченное значение * /) = valueToBeExtracted
  • valueToBeExtracted match {case extractor (extractValue1, _) => ???}
  • val (tuple1, tuple2, tuple3) = tupleWith3Elements
  • объект Foo {def unapply (foo: Foo): Option [String] = Some (foo.x); }

Экстракторы кортежей

x и y извлекаются из кортежа:

val (x, y) = (1337, 42)
// x: Int = 1337
// y: Int = 42

Чтобы игнорировать значение, используйте _ :

val (_, y: Int) = (1337, 42)
// y: Int = 42

Чтобы распаковать экстрактор:

val myTuple = (1337, 42)
myTuple._1  // res0: Int = 1337
myTuple._2  // res1: Int = 42

Обратите внимание, что кортежи имеют максимальную длину 22 и, следовательно, будет работать от ._1 до ._22 (при условии, что кортеж имеет по крайней мере такой размер).

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

val persons = List("A." -> "Lovelace", "G." -> "Hopper")
val names = List("Lovelace, A.", "Hopper, G.")

assert {
  names ==
    (persons map { name =>
      s"${name._2}, ${name._1}"
    })
}

assert {
  names ==
    (persons map { case (given, surname) =>
      s"$surname, $given"
    })
}

Экстракторы корпуса

Класс case - это класс с большим количеством стандартных шаблонов. Одно из преимуществ этого заключается в том, что Scala упрощает использование экстракторов с классами case.

case class Person(name: String, age: Int)  // Define the case class
val p = Person("Paola", 42)  // Instantiate a value with the case class type

val Person(n, a) = p  // Extract values n and a
// n: String = Paola
// a: Int = 42

На этом этапе, как n и a являются val s в программе и могут быть доступны как таковые: они, как говорят, были «извлечены» из p. Продолжение:

val p2 = Person("Angela", 1337)

val List(Person(n1, a1), Person(_, a2)) = List(p, p2)
// n1: String = Paola
// a1: Int = 42
// a2: Int = 1337

Здесь мы видим две важные вещи:

  • Экстракция может происходить на «глубоких» уровнях: можно выделить свойства вложенных объектов.
  • Не все элементы должны быть извлечены. Подстановочный _ символ указывает на то , что именно эта величина может быть что угодно, и игнорируется. Никакой val не создается.

В частности, это может упростить сопоставление по коллекциям:

val ls = List(p1, p2, p3)  // List of Person objects
ls.map(person => person match {
  case Person(n, a) => println("%s is %d years old".format(n, a))
})

Здесь у нас есть код, который использует экстрактор, чтобы явно проверить, что person является объектом Person и немедленно вытащить переменные, которые нас волнуют: n и a .

Unapply - Пользовательские экстракторы

Пользовательское извлечение может быть записано путем реализации метода unapply и возврата значения типа Option :

class Foo(val x: String)

object Foo {
  def unapply(foo: Foo): Option[String] = Some(foo.x)
}

new Foo("42") match {
  case Foo(x) => x
}
// "42"

Возвращаемый тип unapply может быть чем-то иным, чем Option , если возвращаемый тип предоставляет методы get и isEmpty . В этом примере Bar определяется этими методами, а unapply возвращает экземпляр Bar :

class Bar(val x: String) {
  def get = x
  def isEmpty = false
}

object Bar {
  def unapply(bar: Bar): Bar = bar
}

new Bar("1337") match {
  case Bar(x) => x
}
// "1337"

Возвращаемый тип unapply также может быть Boolean , что является особым случаем, который не несет требований get и isEmpty выше. Однако обратите внимание в этом примере, что DivisibleByTwo - это объект, а не класс, и не принимает параметр (и, следовательно, этот параметр не может быть связан):

object DivisibleByTwo {
  def unapply(num: Int): Boolean = num % 2 == 0
}

4 match {                        
  case DivisibleByTwo() => "yes" 
  case _ => "no"
}
// yes

3 match {
  case DivisibleByTwo() => "yes"
  case _ => "no"
}
// no

Помните, что unapply идет в сопутствующем объекте класса, а не в классе. Приведенный выше пример будет ясен, если вы поймете это различие.

Инфраструктура Extractor Infix

Если класс case имеет ровно два значения, его экстрактор может использоваться в нотации infix.

case class Pair(a: String, b: String)
val p: Pair = Pair("hello", "world")
val x Pair y = p
//x: String = hello
//y: String = world

Любой экстрактор, который возвращает 2-кортеж, может работать таким образом.

object Foo {
    def unapply(s: String): Option[(Int, Int)] = Some((s.length, 5))
}
val a Foo b = "hello world!"
//a: Int = 12
//b: Int = 5

Экстракторы регулярных выражений

Регулярное выражение с группируемыми частями может использоваться как экстрактор:

scala> val address = """(.+):(\d+)""".r
address: scala.util.matching.Regex = (.+):(\d+)

scala> val address(host, port) = "some.domain.org:8080"
host: String = some.domain.org
port: String = 8080

Обратите внимание, что когда он не сопоставляется, MatchError будет MatchError во время выполнения:

scala> val address(host, port) = "something not a host and port"
scala.MatchError: something not a host and port (of class java.lang.String)

Трансформаторные экстракторы

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

Рассмотрим в качестве примера различные форматы имен пользователей, которые можно использовать в среде Windows :

object UserPrincipalName {
  def unapply(str: String): Option[(String, String)] = str.split('@') match {
    case Array(u, d) if u.length > 0 && d.length > 0 => Some((u, d))
    case _ => None
  }        
}

object DownLevelLogonName {
  def unapply(str: String): Option[(String, String)] = str.split('\\') match {
    case Array(d, u) if u.length > 0 && d.length > 0 => Some((d, u))
    case _ => None
  }
}

def getDomain(str: String): Option[String] = str match {
  case UserPrincipalName(_, domain) => Some(domain)
  case DownLevelLogonName(domain, _) => Some(domain)
  case _ => None
}

На самом деле можно создать экстрактор, демонстрирующий оба поведения, путем расширения типов, которые он может сопоставить:

object UserPrincipalName {
  def unapply(obj: Any): Option[(String, String)] = obj match {
    case upn: UserPrincipalName => Some((upn.username, upn.domain))
    case str: String => str.split('@') match {
      case Array(u, d) if u.length > 0 && d.length > 0 => Some((u, d))
      case _ => None
    }
    case _ => None
  }        
}

В общем, экстракторы - это просто удобная переформатирование шаблона Option , применительно к методам с именами, такими как tryParse :

UserPrincipalName.unapply("user@domain") match {
  case Some((u, d)) => ???
  case None => ???
}


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