Lanzando excepciones en Scala, ¿cuál es la "regla oficial"?
exception try-catch (2)
Así que este es uno de esos lugares en los que Scala intercambia específicamente la pureza funcional para facilitar la transición desde / interoperabilidad con los entornos y lenguajes heredados, específicamente Java. La pureza funcional se rompe por excepciones, ya que rompen la integridad referencial y hacen que sea imposible razonar de manera ecuacional. (Por supuesto, las recursiones no terminadas hacen lo mismo, pero pocos idiomas están dispuestos a aplicar las restricciones que harían imposible). Para mantener la pureza funcional, use Option / Maybe / Oither / Try / Validation, que codifican el éxito. o falla como tipo referencialmente transparente, y use las diversas funciones de orden superior que proporcionan o la sintaxis especial de monad de idiomas subyacentes para aclarar las cosas. O bien, en Scala, puede simplemente decidir abandonar la pureza funcional, sabiendo que podría facilitar las cosas a corto plazo, pero a la larga será más difícil. Esto es similar al uso de "nulo" en Scala, o colecciones mutables, o "var" locales. Algo vergonzoso, y no lo hago en gran parte, pero todos tienen un plazo límite.
Estoy siguiendo el curso de Scala en Coursera. También comencé a leer el libro de Scala de Odersky.
Lo que a menudo escucho es que no es una buena idea lanzar excepciones en los lenguajes funcionales, porque rompe el flujo de control y usualmente devolvemos un O bien con el Fracaso o el Éxito. También parece que Scala 2.10 proporcionará la Prueba que va en esa dirección.
Pero en el libro y el curso, Martin Odersky no parece decir (al menos por ahora) que las excepciones son malas, y las usa mucho. También noté que los métodos afirman / requieren ...
Finalmente estoy un poco confundido porque me gustaría seguir las mejores prácticas pero no están claras y el lenguaje parece ir en ambas direcciones ...
¿Puede alguien explicarme qué debo usar en qué caso?
La pauta básica es usar excepciones para algo realmente excepcional **. Para una falla "normal", es mucho mejor usar Option
o Either
. Si está interactuando con Java, donde se producen excepciones cuando alguien estornuda de la manera incorrecta, puede usar Try
para mantenerse a salvo.
Tomemos algunos ejemplos.
Supongamos que tiene un método que obtiene algo de un mapa. ¿Qué puede salir mal? Bueno, algo dramático y peligroso como un desbordamiento de pila segfault *, o algo esperado como que el elemento no se encuentra. Permitiría que el desbordamiento de la pila segfault arroje una excepción, pero si simplemente no encuentra un elemento, ¿por qué no devuelve una Option[V]
lugar del valor o una excepción (o null
)?
Ahora suponga que está escribiendo un programa donde se supone que el usuario debe ingresar un nombre de archivo. Ahora, si no vas a fianza instantáneamente en el programa cuando algo sale mal, un Either
es el camino a seguir:
def main(args: Array[String]) {
val f = {
if (args.length < 1) Left("No filename given")
else {
val file = new File(args(0))
if (!file.exists) Left("File does not exist: "+args(0))
else Right(file)
}
}
// ...
}
Ahora supongamos que quiere analizar una cadena con números delimitados por espacios.
val numbers = "1 2 3 fish 5 6" // Uh-oh
// numbers.split(" ").map(_.toInt) <- will throw exception!
val tried = numbers.split(" ").map(s => Try(s.toInt)) // Caught it!
val good = tried.collect{ case Success(n) => n }
Por lo tanto, tiene tres formas (al menos) de lidiar con diferentes tipos de falla: Option
para que funcionó / no funcionó, en los casos en que no funciona, se espera un comportamiento, no es una falla impactante y alarmante; Either
para cuando las cosas pueden funcionar o no (o, en realidad, cualquier caso en el que tenga dos opciones mutuamente excluyentes) y desea guardar información sobre lo que salió mal; y Try
cuando no desee controlarse por completo, pero aún así necesita interactuar con un código que sea excepcionalmente feliz.
Por cierto, las excepciones son buenos ejemplos, por lo que los encontrarán más a menudo en un libro de texto o material de aprendizaje que en cualquier otro lugar. Los ejemplos de libros de texto a menudo son incompletos, lo que significa que problemas serios que normalmente se evitarían con un diseño cuidadoso en su lugar se marcarán lanzando una excepción.
* Editar: Segfaults bloquea la JVM y nunca debería pasar independientemente del bytecode; incluso una excepción no te ayudará entonces. Me refería al desbordamiento de la pila.
** Editar: las excepciones (sin un seguimiento de la pila) también se utilizan para controlar el flujo en Scala: en realidad son un mecanismo bastante eficiente y permiten cosas como declaraciones de break
definidas por la biblioteca y un return
que regresa de su método aunque el control ha pasado realmente a uno o más cierres. En general, no debe preocuparse por esto usted mismo, excepto para darse cuenta de que la captura de todos los Throwable
s no es una idea tan buena ya que podría tomar una de estas excepciones de flujo de control por error.