Scala Language
Optieklasse
Zoeken…
Syntaxis
klasse Sommige [+ T] (waarde: T) breidt optie [T] uit
object Geen verlengt Optie [Niets]
Optie [T] (waarde: T)
Constructor om een
Some(value)
ofNone
te maken, afhankelijk van de opgegeven waarde.
Opties als collecties
Option
hebben een aantal nuttige functies van hogere orde die gemakkelijk kunnen worden begrepen door opties te bekijken als collecties met nul of één items - waarbij None
zich gedraagt als de lege collectie en Some(x)
zich gedraagt als een collectie met een enkel item, 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
Optie gebruiken in plaats van Null
In Java (en andere talen) is het gebruik van null
een veelgebruikte manier om aan te geven dat er geen waarde is gekoppeld aan een referentievariabele. In Scala heeft het gebruik van Option
voorkeur boven het gebruik van null
. Option
verpakt waarden die mogelijk null
.
None
is een subklasse van Option
een nulreferentie. Some
is een subklasse van Option
een niet-nulreferentie.
Een referentie wikkelen is eenvoudig:
val nothing = Option(null) // None
val something = Option("Aren't options cool?") // Some("Aren't options cool?")
Dit is typische code bij het aanroepen van een Java-bibliotheek die mogelijk een nulreferentie retourneert:
val resource = Option(JavaLib.getResource())
// if null, then resource = None
// else resource = Some(resource)
Als getResource()
een null
retourneert, is resource
een object None
. Anders wordt het een Some(resource)
object. De voorkeursmanier om een Option
te handelen, is gebruik te maken van functies van hogere orde die beschikbaar zijn binnen het Option
. Als u bijvoorbeeld wilt controleren of uw waarde niet None
(vergelijkbaar met controleren of value == null
), gebruikt u de functie isDefined
:
val resource: Option[Resource] = Option(JavaLib.getResource())
if (resource.isDefined) { // resource is `Some(_)` type
val r: Resource = resource.get
r.connect()
}
Op dezelfde manier, om te controleren of een null
referentie kunt u dit doen:
val resource: Option[Resource] = Option(JavaLib.getResource())
if (resource.isEmpty) { // resource is `None` type.
System.out.println("Resource is empty! Cannot connect.")
}
Het heeft de voorkeur dat u voorwaardelijke uitvoering behandelt volgens de verpakte waarde van een Option
(zonder de 'exceptionele' Option.get
methode te gebruiken) door de Option
als een monade te behandelen en 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
Als een Resource
vereist is (versus een Option[Resource]
instantie), kunt u Option
nog steeds gebruiken om te beschermen tegen nulwaarden. Hier biedt de methode getOrElse
een standaardwaarde:
lazy val defaultResource = new Resource()
val resource: Resource = Option(JavaLib.getResource()).getOrElse(defaultResource)
Java-code zal Scala's Option
niet gemakkelijk verwerken, dus wanneer waarden aan Java-code worden doorgegeven, is het een goede vorm om een Option
te pakken, null
of een verstandige standaard indien van toepassing:
val resource: Option[Resource] = ???
JavaLib.sendResource(resource.orNull)
JavaLib.sendResource(resource.getOrElse(defaultResource)) //
Basics
Een Option
is een gegevensstructuur die een enkele waarde of helemaal geen waarde bevat. Een Option
kan worden gezien als verzamelingen van nul of één elementen.
Option is een abstracte klas met twee kinderen: Some
en None
.
Some
bevatten een enkele waarde en None
bevat geen waarde.
Option
is handig in uitdrukkingen die anders null
zouden gebruiken om het ontbreken van een concrete waarde weer te geven. Dit beschermt tegen een NullPointerException
en maakt de samenstelling mogelijk van veel expressies die mogelijk geen waarde retourneren met behulp van combinators zoals Map
, FlatMap
, etc.
Voorbeeld met kaart
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]
is verzegeld en kan dus niet worden uitgebreid. Daarom is de semantiek stabiel en kan erop worden vertrouwd.
Opties voor begrip
Option
hebben een flatMap
methode. Dit betekent dat ze kunnen worden gebruikt in een voor begrip. Op deze manier kunnen we reguliere functies naar Option
s tillen zonder ze opnieuw te definiëren.
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)
Als een van de waarden None
het eindresultaat van de berekening ook 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
Opmerking: dit patroon is meer algemeen van toepassing op concepten die Monad
s worden genoemd. (Meer informatie zou beschikbaar moeten zijn op pagina's met betrekking tot begrip en Monad
)
Over het algemeen is het niet mogelijk om verschillende monaden voor een goed begrip te mixen. Maar omdat Option
eenvoudig kan worden omgezet in een Iterable
, kunnen we Option
s en Iterable
s gemakkelijk mixen door de .toIterable
methode aan te roepen.
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)
Een kleine opmerking: als we ons begrip voor begrip hadden gedefinieerd, zou andersom het begrip bevatten, omdat onze optie impliciet zou worden omgezet. Daarom is het handig om altijd .toIterable
(of de bijbehorende functie, afhankelijk van de verzameling die u gebruikt) toe te voegen voor consistentie.