Szukaj…


Składnia

  • klasa Niektóre [+ T] (wartość: T) rozszerza opcję [T]

  • obiekt Brak rozszerza Opcja [Nic]

  • Opcja [T] (wartość: T)

    Konstruktor, aby utworzyć wartość Some(value) lub None stosownie do podanej wartości.

Opcje jako kolekcje

Option mają kilka użytecznych funkcji wyższego rzędu, które można łatwo zrozumieć, przeglądając opcje jako kolekcje z zerową lub jedną pozycją - gdzie None zachowuje się jak pusta kolekcja, a Some(x) zachowują się jak kolekcja z pojedynczą pozycją, x .

val option: Option[String] = ???

option.map(_.trim) // None if option is None, Some(s.trim) if Some(s)
option.foreach(println) // prints the string if it exists, does nothing otherwise
option.forall(_.length > 4) // true if None or if Some(s) and s.length > 4
option.exists(_.length > 4) // true if Some(s) and s.length > 4
option.toList // returns an actual list

Korzystanie z opcji zamiast zera

W Javie (i innych językach) użycie null jest powszechnym sposobem wskazania, że do zmiennej referencyjnej nie przypisano żadnej wartości. W Scali użycie Option jest lepsze niż użycie null . Option zawija wartości, które mogą być null .

None jest podklasą Option zawierającą odwołanie zerowe. Some są podklasą Option zawierającą odwołanie inne niż null.

Zawijanie referencji jest łatwe:

val nothing = Option(null) // None
val something = Option("Aren't options cool?") // Some("Aren't options cool?")

Jest to typowy kod podczas wywoływania biblioteki Java, która może zwrócić odwołanie zerowe:

val resource = Option(JavaLib.getResource())
// if null, then resource = None
// else resource = Some(resource)

Jeśli getResource() zwróci wartość null , resource będzie obiektem None . W przeciwnym razie będzie to obiekt Some(resource) . Preferowanym sposobem obsługi Option jest użycie funkcji wyższego rzędu dostępnych w ramach typu Option . Na przykład, jeśli chcesz sprawdzić, czy twoja wartość nie jest None (podobnie do sprawdzania, czy value == null ), skorzystasz z funkcji isDefined :

val resource: Option[Resource] = Option(JavaLib.getResource())
if (resource.isDefined) {  // resource is `Some(_)` type
  val r: Resource = resource.get
  r.connect()
}

Podobnie, aby sprawdzić null odwołanie, możesz to zrobić:

val resource: Option[Resource] = Option(JavaLib.getResource())
if (resource.isEmpty) { // resource is `None` type.
  System.out.println("Resource is empty! Cannot connect.")
}

Preferowane jest traktowanie wykonania warunkowego na opakowanej wartości Option (bez użycia „wyjątkowej” metody Option.get ), traktując Option jako monadę i używając foreach :

val resource: Option[Resource] = Option(JavaLib.getResource())
resource foreach (r => r.connect())
// if r is defined, then r.connect() is run
// if r is empty, then it does nothing

Jeśli wymagana jest instancja Resource (w porównaniu z instancją Option[Resource] ), można nadal używać Option celu ochrony przed wartościami zerowymi. Tutaj metoda getOrElse podaje wartość domyślną:

lazy val defaultResource = new Resource()
val resource: Resource = Option(JavaLib.getResource()).getOrElse(defaultResource)

Kod Java nie poradzi sobie łatwo z Option Scali, więc przy przekazywaniu wartości do kodu Java dobrym rozwiązaniem jest rozpakowanie Option , przekazanie null lub rozsądne ustawienie domyślne, w stosownych przypadkach:

val resource: Option[Resource] = ???
JavaLib.sendResource(resource.orNull)
JavaLib.sendResource(resource.getOrElse(defaultResource)) // 

Podstawy

Option to struktura danych, która zawiera albo pojedynczą wartość, albo w ogóle jej nie ma. Option można traktować jako zbiór zer lub jednego elementu.

Opcja to klasa abstrakcyjna z dwójką dzieci: Some i None .

Some zawierają jedną wartość, a None nie zawiera żadnej wartości.

Option jest przydatna w wyrażeniach, które w innym przypadku null do przedstawienia braku konkretnej wartości. Chroni to przed NullPointerException i umożliwia składanie wielu wyrażeń, które mogą nie zwracać wartości za pomocą kombinacji, takich jak Map , FlatMap itp.

Przykład z mapą

val countries = Map(
  "USA" -> "Washington",
  "UK" -> "London",
  "Germany" -> "Berlin",
  "Netherlands" -> "Amsterdam",
  "Japan" -> "Tokyo"
)
    
println(countries.get("USA")) // Some(Washington)
println(countries.get("France")) // None
println(countries.get("USA").get) // Washington
println(countries.get("France").get) // Error: NoSuchElementException
println(countries.get("USA").getOrElse("Nope")) // Washington
println(countries.get("France").getOrElse("Nope")) // Nope

Option[A] jest zapieczętowana i dlatego nie można jej rozszerzyć. Dlatego jego semantyka jest stabilna i można na niej polegać.

Opcje do zrozumienia

Option mają metodę flatMap . Oznacza to, że można je wykorzystać do zrozumienia. W ten sposób możemy podnieść zwykłe funkcje do pracy na Option bez konieczności ich ponownego definiowania.

val firstOption: Option[Int] = Option(1)
val secondOption: Option[Int] = Option(2)

val myResult = for {
  firstValue <- firstOption
  secondValue <- secondOption
} yield firstValue + secondValue
// myResult: Option[Int] = Some(3)

Gdy jedną z wartości jest None końcowym wynikiem obliczeń będzie również None .

val firstOption: Option[Int] = Option(1)
val secondOption: Option[Int] = None

val myResult = for {
  firstValue <- firstOption
  secondValue <- secondOption
} yield firstValue + secondValue
// myResult: Option[Int] = None

Uwaga: wzorzec ten rozciąga się bardziej ogólnie na pojęcia zwane Monad . (Więcej informacji powinno być dostępnych na stronach związanych ze zrozumieniem i Monad )

Zasadniczo nie jest możliwe mieszanie różnych monad w celu zrozumienia. Ale ponieważ Option może być łatwo przekształcony w Iterable , możemy łatwo mieszać Option s i Iterable s wywołując .toIterable metody.

val option: Option[Int] = Option(1)
val iterable: Iterable[Int] = Iterable(2, 3, 4, 5)

// does NOT compile since we cannot mix Monads in a for comprehension
// val myResult = for {
//   optionValue <- option
//   iterableValue <- iterable
//} yield optionValue + iterableValue

// It does compile when adding a .toIterable on the option
val myResult = for {
  optionValue <- option.toIterable
  iterableValue <- iterable
} yield optionValue + iterableValue
// myResult: Iterable[Int] = List(2, 3, 4, 5)

Mała uwaga: gdybyśmy zdefiniowali nasze do zrozumienia, kompilacja odwrotna doszłaby do skutku, ponieważ nasza opcja byłaby niejawnie przekształcona. Z tego powodu warto zawsze dodawać .toIterable (lub odpowiednią funkcję w zależności od .toIterable kolekcji) w celu zachowania spójności.



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow