Scala Language
Ekstraktory
Szukaj…
Składnia
- val extractor (extractValue1, _ / * zignorowano drugą wyodrębnioną wartość * /) = valueToBeExtracted
- valueToBeExtracted match {case extractor (extractValue1, _) => ???}
- val (tuple1, tuple2, tuple3) = tupleWith3Elements
- obiekt Foo {def unapply (foo: Foo): Option [String] = Some (foo.x); }
Ekstraktory krotowe
x
i y
są ekstrahowane z krotki:
val (x, y) = (1337, 42) // x: Int = 1337 // y: Int = 42
Aby zignorować wartość, użyj _
:
val (_, y: Int) = (1337, 42) // y: Int = 42
Aby rozpakować ekstraktor:
val myTuple = (1337, 42) myTuple._1 // res0: Int = 1337 myTuple._2 // res1: Int = 42
Zauważ, że krotki mają maksymalną długość 22, a zatem od ._1
do ._22
będą działać (zakładając, że krotka ma co najmniej taki rozmiar).
Ekstraktory krotkowe mogą być użyte do przedstawienia argumentów symbolicznych dla funkcji dosłownych:
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" }) }
Ekstraktory klasy skrzynek
Klasa skrzynki to klasa z automatycznie dołączoną dużą ilością standardowego kodu płyty kotłowej. Jedną z korzyści jest to, że Scala ułatwia korzystanie z ekstraktorów z klasami przypadków.
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
W tym momencie, zarówno n
i są a
val
s w programie, można uzyskać w następujący sposób: są one mówi się, że „ekstrakcji” ze s. Kontynuując:
val p2 = Person("Angela", 1337)
val List(Person(n1, a1), Person(_, a2)) = List(p, p2)
// n1: String = Paola
// a1: Int = 42
// a2: Int = 1337
Tutaj widzimy dwie ważne rzeczy:
- Ekstrakcja może odbywać się na „głębokich” poziomach: można wyodrębnić właściwości zagnieżdżonych obiektów.
- Nie wszystkie elementy muszą zostać wyodrębnione. Symbol wieloznaczny
_
wskazuje, że ta konkretna wartość może być dowolna, i jest ignorowany. Nie utworzonoval
.
W szczególności może to ułatwić dopasowanie do kolekcji:
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))
})
Tutaj mamy kod, który używa ekstraktora, aby jawnie sprawdzić, czy person
jest obiektem Person
i natychmiast wyciągnąć zmienne, na których nam zależy: n
oraz a
.
Unapply - niestandardowe ekstraktory
Niestandardowe wyodrębnienie można napisać, implementując metodę unapply
i zwracając wartość typu 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"
Typem zwracanym unapply
może być coś innego niż Option
, pod warunkiem, że zwrócony typ zapewnia metody get
i isEmpty
. W tym przykładzie Bar
jest definiowany za pomocą tych metod i unapply
zwraca instancję 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"
Typ powrót unapply
można również Boolean
, która jest szczególnym przypadkiem, że nie niosą get
i isEmpty
powyższych wymagań. Należy jednak zauważyć w tym przykładzie, że DivisibleByTwo
jest obiektem, a nie klasą i nie przyjmuje parametru (a zatem tego parametru nie można powiązać):
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
Pamiętaj, że unapply
trafia do obiektu towarzyszącego klasy, a nie do klasy. Powyższy przykład będzie jasny, jeśli zrozumiesz to rozróżnienie.
Notacja ekstraktora
Jeśli klasa sprawy ma dokładnie dwie wartości, jej ekstraktor może być użyty w notacji 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
Każdy ekstraktor, który zwraca 2-krotkę, może działać w ten sposób.
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
Ekstraktory Regex
Jako ekstraktor można użyć wyrażenia regularnego z pogrupowanymi częściami:
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
Zauważ, że jeśli nie zostanie dopasowany, MatchError
zostanie wygenerowany w czasie wykonywania:
scala> val address(host, port) = "something not a host and port"
scala.MatchError: something not a host and port (of class java.lang.String)
Ekstrakty transformujące
Zachowanie ekstraktora można wykorzystać do uzyskania dowolnych wartości z ich danych wejściowych. Może to być przydatne w scenariuszach, w których chcesz mieć wpływ na wyniki transformacji w przypadku pomyślnej transformacji.
Rozważ jako przykład różne formaty nazw użytkowników, które można wykorzystać w środowisku 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
}
W rzeczywistości można utworzyć ekstraktor wykazujący oba zachowania, rozszerzając typy, które można dopasować:
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
}
}
Ogólnie rzecz biorąc, ekstraktory są po prostu wygodnym przeformułowaniem wzorca Option
, stosowanym do metod o nazwach takich jak tryParse
:
UserPrincipalName.unapply("user@domain") match {
case Some((u, d)) => ???
case None => ???
}