Lanzar excepciones en Scala, ¿cuál es la " regla oficial"


Estoy siguiendo el curso de Scala en Coursera. También he empezado a leer el libro de la Scala de Odersky.

Lo que escucho a menudo es que no es una buena idea lanzar excepciones en lenguajes funcionales, porque rompe el flujo de control y generalmente devolvemos una ya sea con el Fracaso o el Éxito. Parece también que Scala 2.10 proporcionará el Intento 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 me di cuenta de los métodos assert / require...

Finalmente estoy un poco confundido porque me gustaría seguir las mejores prácticas, pero no son claras y el lenguaje parece ir en ambas direcciones...

¿Puede alguien explicarme qué debo usar en ese caso?

Author: Sebastien Lorber, 2012-10-15

2 answers

La guía básica es usar excepciones para algo realmente excepcional**. Para un fallo "ordinario", es mucho mejor usar Option o Either. Si está interactuando con Java donde se producen excepciones cuando alguien estornuda de 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é podría salir mal? Bueno, algo dramático y peligroso como un segfault * desbordamiento de pila, o algo esperado como el elemento no se encuentra. Dejarías que el desbordamiento de pila segfault arroje una excepción, pero si simplemente no encuentras un elemento, ¿por qué no devuelves un Option[V] en lugar del valor o una excepción (o null)?

Ahora supongamos que está escribiendo un programa donde se supone que el usuario debe ingresar un nombre de archivo. Ahora, si no solo vas a abandonar instantáneamente 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 desea 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 }

Así que tienes tres maneras (al menos) de lidiar con diferentes tipos de fallas: Option para que funcionó / no funcionó, en los casos en que no funciona se espera un comportamiento, no un fracaso impactante y alarmante; Either para cuando las cosas pueden funcionar o no (o, realmente, cualquier caso en que tiene dos opciones mutuamente excluyentes) y desea guardar alguna información sobre lo que salió mal; y Try cuando no desea que todo el dolor de cabeza de el manejo de excepciones usted mismo, pero todavía necesita interactuar con el código que es excepción-feliz.

Por cierto, las excepciones hacen buenos ejemplos so por lo que los encontrará más a menudo en un libro de texto o material de aprendizaje que en cualquier otro lugar, creo: los ejemplos de libros de texto son muy a menudo incompletos, lo que significa que los problemas graves que normalmente se evitarían mediante un diseño cuidadoso deben ser marcados lanzando una excepción.

*Editar: Los valores de segmento bloquean la JVM y nunca deberían suceda independientemente del bytecode; incluso una excepción no le ayudará entonces. Quise decir desbordamiento de pila.

**Edit: Excepciones (sin una traza de la pila) también se utilizan para el control de flujo en la Scala--en realidad son bastante un mecanismo eficiente, y permiten cosas como la biblioteca definida por break declaraciones y un return que regresa de su método, incluso a pesar de que el control tiene en realidad se convirtió en uno o más cierres de empresas. Sobre todo, usted no debe preocuparse por esto usted mismo, excepto para darse cuenta que la captura todos Throwables no es una idea tan estupenda, ya que podría atrapar una de estas excepciones de flujo de control por error.

 47
Author: Rex Kerr,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-10-14 22:57:15

Así que este es uno de esos lugares donde Scala cambia específicamente la pureza funcional por la facilidad de transición desde/interoperabilidad con lenguajes y entornos heredados, específicamente Java. La pureza funcional se rompe por las excepciones, ya que rompen la integridad referencial y hacen imposible razonar ecuacionalmente. (Por supuesto, las recursiones no terminantes hacen lo mismo, pero pocos idiomas están dispuestos a hacer cumplir las restricciones que las harían imposibles.) Para mantener la pureza funcional, se utiliza la opción/Maybe/Either/Try/Validation, que codifica el éxito o el fracaso como un tipo referencialmente transparente, y se utilizan las diversas funciones de orden superior que proporcionan o la sintaxis especial de mónadas de los lenguajes subyacentes para hacer las cosas más claras. O, en Scala, simplemente puede decidir deshacerse de la pureza funcional, sabiendo que podría hacer las cosas más fáciles a corto plazo pero más difíciles a largo plazo. Esto es similar a usar "null" en Scala, o colecciones mutables, o "var" locales. vergonzoso, y no hacer mucho de él, pero todo el mundo está bajo plazo.

 11
Author: Dave Griffith,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-10-15 14:02:11