scala - renault - Cuándo usar la opción
scala vs java (4)
Cuando se trata de programación funcional en Scala, Option
es mucho más preferible que null
ya que es de tipo seguro y juega bien con otras construcciones en el paradigma funcional.
Especialmente, puede escribir fácilmente código idiomático usando funciones de alto orden en Option
. Esta hoja de referencia de opciones de Scala es una lectura útil sobre el tema.
Aprendo Scala por algún tiempo y no puedo entender claramente el uso de la Opción. Me ayuda a evitar las comprobaciones nulas cuando encadeno funciones (según los docs ). Eso está claro para mí :)
A continuación, veo que la opción puede ser un tipo de indicador para el desarrollador de que aquí es posible un valor nulo y debe manejarse. ¿Es verdad? Si es así, ¿debo usar la opción donde sea posible? Por ejemplo
class Racer {
val car = new Car()
}
Tengo una clase de Racer y estoy seguro de que el campo del coche no puede ser nulo (porque es constante y obtiene un valor cuando creo la instancia de Racer). No hay necesidad de opción aquí.
class Racer {
var car = new Car()
}
Aquí lo hago para que el coche pueda cambiar. Y es posible que alguien le asigne nulo al auto. ¿Debo usar la opción aquí? Si es así, me doy cuenta de que todos los campos de mi clase son candidatos para la opción. Y mi código se ve así
class Racer {
var car: Option[Car] = None
var currentRace: Option[Race] = None
var team: Option[Team] = None
...
}
¿Se ve bien? Para mí parece una especie de sobreuso de opción.
def foo(): Result = {
if( something )
new Result()
else
null
}
Tengo un método que puede devolver nulo. ¿Debo devolver la opción en su lugar? ¿Debo hacerlo siempre si es posible que un método devuelva nulo?
Cualquier pensamiento al respecto sería útil. ¡Gracias por adelantado!
Mi pregunta es similar a la opción Por qué, pero creo que no es lo mismo. Es más sobre cuándo, no por qué. :)
Debes evitar el null
tanto como sea posible en Scala. Realmente solo existe para la interoperabilidad con Java. Entonces, en lugar de null
, use Option
siempre que sea posible que una función o método pueda devolver "ningún valor", o cuando sea lógicamente válido para una variable miembro que no tenga "ningún valor".
Con respecto a tu ejemplo de Racer
: ¿Es realmente válido un Racer
si no tiene un car
, una currentRace
y un team
? Si no es así, entonces no debería hacer esas opciones de variables miembro. No solo haga las opciones porque teóricamente es posible asignarlas a null
; hágalo solo si, lógicamente, lo considera un objeto Racer
válido si alguna de esas variables miembro no tiene valor.
En otras palabras, es mejor fingir que null
no existe. El uso de null
en el código Scala es un olor de código.
def foo(): Option[Result] = if (something) Some(new Result()) else None
Tenga en cuenta que la Option
tiene muchos métodos útiles para trabajar.
val opt = foo()
// You can use pattern matching
opt match {
case Some(result) => println("The result is: " + result)
case None => println("There was no result!")
}
// Or use for example foreach
opt foreach { result => println("The result is: " + result) }
Además, si desea programar en el estilo funcional, debe evitar los datos mutables tanto como sea posible, lo que significa: evite el uso de var
, use val
lugar, use colecciones inmutables y haga que sus propias clases sean lo más inmutables posible.
La idea detrás de Options
es deshacerse de null
, así que sí, debes usarlas en lugar de devolver " null
o un valor". Por eso también, si desea modelar campos opcionales (relación 0..1 con otros objetos), usar Option
es definitivamente una buena cosa.
El pequeño inconveniente es que hace que la declaración de clase con un montón de campos opcionales sea un poco detallada, como mencionó.
Una cosa más, en Scala, se recomienda utilizar "objetos inmutables", por lo que en su ejemplo, los campos deben tener algunos val
. ;)
Si bien Option
puede hacer que su definición de clase tenga un aspecto detallado, la alternativa es no saber cuándo necesita probar si una variable está definida. Creo que en su ejemplo, si su clase fuera inmutable (todos los campos eran val
s), no necesitaría tantas Option
.
Para su método foo
, si devuelve un valor nulo, debe documentarlo y el cliente debe leer esa documentación. Luego el cliente escribirá un montón de código como:
val result = foo(x)
if (result != null) {
println(result)
}
Si, en cambio, define foo
para devolver una Option[Result]
, el sistema de tipos obliga al cliente a manejar la posibilidad de un resultado no definido. También tienen todo el poder de las clases de colecciones.
result foreach println
Una ventaja adicional de usar el método de colecciones con una Option
lugar de probar el null
es que si su método se extiende a una colección de elementos múltiples (como una List
), es posible que no necesite cambiar su código en absoluto. Por ejemplo, inicialmente puede suponer que su Racer
solo puede tener una única tripulación, por lo que define val crew: Option[Crew]
y puede probar si la tripulación es mayor de 30 con crew.forall(_.age > 30).getOrElse(false)
. Ahora, si ha cambiado la definición a val crew: List[Crew]
su código anterior aún compilaría y ahora verificará si todos los miembros de su tripulación tienen más de 30.
A veces necesita tratar con null
porque las bibliotecas que está utilizando pueden devolverlo. Por ejemplo, cuando obtiene un recurso, puede obtener un resultado null
. Afortunadamente, es fácil envolver los resultados de manera defensiva para que se transformen en una opción.
val resource = Option(this.getClass.getResource("foo"))
Si getResource
devolvió un null
, el resource
es igual a None
y, de lo contrario, se trata de un Some[URL]
. ¡Guay!
Desafortunadamente, es probable que la Option
tenga algo de sobrecarga porque implica la creación de un objeto adicional. Sin embargo, esa sobrecarga es mínima en comparación con una excepción de puntero nulo!