Scala Language
Clase de opción
Buscar..
Sintaxis
clase Algunos [+ T] (valor: T) extiende la opción [T]
objeto Ninguno extiende la Opción [Nada]
Opción [T] (valor: T)
El constructor creará un
Some(value)
oNone
según sea apropiado para el valor proporcionado.
Opciones como colecciones
Option
s tienen algunas funciones útiles de orden superior que pueden entenderse fácilmente al ver las opciones como colecciones con cero o un solo elemento , donde None
comporta como la colección vacía, y Some(x)
comportan como una colección con un solo elemento, 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
Usando Opción en lugar de Null
En Java (y otros lenguajes), usar null
es una forma común de indicar que no hay un valor asociado a una variable de referencia. En Scala, se prefiere usar Option
en lugar de usar null
. Option
ajusta valores que pueden ser null
.
None
es una subclase de Option
ajusta una referencia nula. Some
es una subclase de Option
envuelve una referencia no nula.
Envolver una referencia es fácil:
val nothing = Option(null) // None
val something = Option("Aren't options cool?") // Some("Aren't options cool?")
Este es un código típico al llamar a una biblioteca de Java que podría devolver una referencia nula:
val resource = Option(JavaLib.getResource())
// if null, then resource = None
// else resource = Some(resource)
Si getResource()
devuelve un valor null
, el resource
será un objeto None
. De lo contrario, será un objeto Some(resource)
. La forma preferida de manejar una Option
es usar funciones de orden superior disponibles dentro del tipo de Option
. Por ejemplo, si desea verificar si su valor no es None
(similar a verificar si el value == null
), usaría la función isDefined
:
val resource: Option[Resource] = Option(JavaLib.getResource())
if (resource.isDefined) { // resource is `Some(_)` type
val r: Resource = resource.get
r.connect()
}
De manera similar, para verificar una referencia null
puede hacer esto:
val resource: Option[Resource] = Option(JavaLib.getResource())
if (resource.isEmpty) { // resource is `None` type.
System.out.println("Resource is empty! Cannot connect.")
}
Es preferible que trate la ejecución condicional en el valor envuelto de una Option
(sin usar el método 'excepcional' Option.get
) tratando la Option
como una mónada y usando 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
Si se requiere una instancia de Resource
lugar de una instancia de Option[Resource]
), aún puede usar la Option
para protegerse contra valores nulos. Aquí el método getOrElse
proporciona un valor predeterminado:
lazy val defaultResource = new Resource()
val resource: Resource = Option(JavaLib.getResource()).getOrElse(defaultResource)
El código Java no manejará fácilmente la Option
de Scala, por lo que al pasar valores al código Java es una buena forma de desenvolver una Option
, pasar un valor null
o un valor predeterminado razonable cuando sea apropiado:
val resource: Option[Resource] = ???
JavaLib.sendResource(resource.orNull)
JavaLib.sendResource(resource.getOrElse(defaultResource)) //
Lo esencial
Una Option
es una estructura de datos que contiene un solo valor o ningún valor en absoluto. Una Option
puede considerarse como colecciones de cero o uno de los elementos.
La opción es una clase abstracta con dos hijos: Some
y None
.
Some
contienen un solo valor, y None
no contiene ningún valor.
Option
es útil en expresiones que de lo contrario usarían null
para representar la falta de un valor concreto. Esto protege contra una NullPointerException
y permite la composición de muchas expresiones que pueden no devolver un valor utilizando combinadores como Map
, FlatMap
, etc.
Ejemplo con mapa
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]
está sellada y, por lo tanto, no puede extenderse. Por lo tanto, su semántica es estable y se puede confiar en ella.
Opciones en para las comprensiones
Option
s tienen un método flatMap
. Esto significa que pueden ser utilizados en una comprensión. De esta manera podemos levantar funciones regulares para trabajar en las Option
s sin tener que redefinirlas.
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)
Cuando uno de los valores es None
el resultado final del cálculo también será 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
Nota: este patrón se extiende más generalmente para los conceptos llamados Monad
s. (Más información debe estar disponible en las páginas relacionadas con las comprensiones y Monad
)
En general, no es posible mezclar diferentes mónadas en una comprensión. Pero dado que la Option
se puede convertir fácilmente en un Iterable
, podemos combinar fácilmente las Option
sy Iterable
llamando al método .toIterable
.
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)
Una pequeña nota: si hubiéramos definido nuestra comprensión para la otra manera, la comprensión se compilaría ya que nuestra opción se convertiría implícitamente. Por esa razón, es útil agregar siempre este .toIterable
(o la función correspondiente según la colección que esté usando) para .toIterable
coherencia.