resueltos - Scala 2.8 control Exception-¿cuál es el punto?
estadistica elemental solucionario (3)
Como el chico que lo escribió, las razones son la composición y la encapsulación. El compilador de scala (y estoy apostando por la mayoría de las bases de origen de tamaño decente) está lleno de lugares que se tragan todas las excepciones (puede verlas con capturas de Ywarn) porque el programador es demasiado vago para enumerar las relevantes, lo cual es comprensible porque es molesto Al hacer posible definir, reutilizar y componer la captura y finalmente los bloques independientemente de la lógica de prueba, esperaba reducir la barrera para escribir bloques sensibles.
Y, no está exactamente terminado, y estoy trabajando en un millón de otras áreas también. Si observa el código de actores, puede ver ejemplos de enormes bloques de prueba / captura / finalmente anidados y multiplicados y pegados. No estaba dispuesto a conformarme con try { catch { try { catch { try { catch ...
Mi plan final es que catch tome un PartialFunction
real en lugar de requerir una lista literal de declaraciones de casos, lo que presenta un nuevo nivel de oportunidad, es decir, try foo() catch getPF()
En la próxima util.control
2.8, se ha agregado un paquete util.control
que incluye una biblioteca de ruptura y una construcción para manejar las excepciones, de modo que el código que parece:
type NFE = NumberFormatException
val arg = "1"
val maybeInt = try { Some(arg.toInt) } catch { case e: NFE => None }
Puede ser reemplazado con código como:
import util.control.Exception._
val maybeInt = catching(classOf[NFE]) opt arg.toInt
Mi pregunta es ¿por qué? ¿Qué agrega esto al lenguaje aparte de proporcionar otra forma (y radicalmente diferente) de expresar lo mismo? ¿Hay algo que se pueda expresar utilizando el nuevo control pero no a través de try-catch
? ¿Se supone que un DSL debe hacer que el manejo de excepciones en Scala se vea como algún otro idioma (y si es así, cuál)?
Hay dos maneras de pensar acerca de las excepciones. Una forma es considerarlos como control de flujo: una excepción cambia el flujo de ejecución del programa, haciendo que la ejecución salte de un lugar a otro. Una segunda forma es pensar en ellos como datos: una excepción es una información sobre la ejecución del programa, que luego puede usarse como entrada a otras partes del programa.
El paradigma try
/ catch
usado en C ++ y Java es muy del primer tipo (*).
Sin embargo, si prefiere tratar las excepciones como datos, tendrá que recurrir a un código como el que se muestra. Para el caso simple, eso es bastante fácil. Sin embargo, cuando se trata del estilo funcional donde la composición es el rey, las cosas empiezan a complicarse. O bien tiene que duplicar el código a su alrededor, o puede crear su propia biblioteca para resolverlo.
Por lo tanto, en un lenguaje que pretende admitir tanto el estilo funcional como el de OO, uno no debe sorprenderse al ver el soporte de la biblioteca para tratar las excepciones como datos.
Y tenga en cuenta que hay muchas otras posibilidades proporcionadas por Exception
para manejar las cosas. Puede, por ejemplo, manipular cadenas, de manera muy parecida a la función Cadena parcial de la cadena de elevación para facilitar la delegación de responsabilidades en el manejo de solicitudes de páginas web.
Este es un ejemplo de lo que se puede hacer, ya que la administración automática de recursos está de moda en estos días:
def arm[T <: java.io.Closeable,R](resource: T)(body: T => R)(handlers: Catch[R]):R = (
handlers
andFinally (ignoring(classOf[Any]) { resource.close() })
apply body(resource)
)
Lo que le brinda un cierre seguro del recurso (tenga en cuenta el uso de ignorar) y aún aplica cualquier lógica de captura que desee utilizar.
(*) Curiosamente, el control de excepciones de Forth, catch
y throw
, es una mezcla de ellos. El flujo salta de un throw
a otro, pero luego esa información se trata como datos.
EDITAR
Ok, ok, cedo. Voy a dar un ejemplo. Un ejemplo, y eso es todo! Espero que esto no sea demasiado artificial, pero no hay forma de evitarlo. Este tipo de cosas serían más útiles en marcos grandes, no en muestras pequeñas.
En cualquier caso, primero definamos algo que hacer con el recurso. Decidí imprimir líneas y devolver el número de líneas impresas, y aquí está el código:
def linePrinter(lnr: java.io.LineNumberReader) = arm(lnr) { lnr =>
var lineNumber = 0
var lineText = lnr.readLine()
while (null != lineText) {
lineNumber += 1
println("%4d: %s" format (lineNumber, lineText))
lineText = lnr.readLine()
}
lineNumber
} _
Aquí está el tipo de esta función:
linePrinter: (lnr: java.io.LineNumberReader)(util.control.Exception.Catch[Int]) => Int
Entonces, arm
recibió un Closeable genérico, pero necesito un LineNumberReader, así que cuando llamo a esta función necesito pasar eso. Sin embargo, lo que devuelvo es una función Catch[Int] => Int
, lo que significa que necesito pasar dos parámetros a linePrinter
para que funcione. Vamos a encontrar un Reader
, ahora:
val functionText = """def linePrinter(lnr: java.io.LineNumberReader) = arm(lnr) { lnr =>
var lineNumber = 1
var lineText = lnr.readLine()
while (null != lineText) {
println("%4d: %s" format (lineNumber, lineText))
lineNumber += 1
lineText = lnr.readLine()
}
lineNumber
} _"""
val reader = new java.io.LineNumberReader(new java.io.StringReader(functionText))
Entonces, ahora, vamos a usarlo. Primero, un ejemplo simple:
scala> linePrinter(new java.io.LineNumberReader(reader))(noCatch)
1: def linePrinter(lnr: java.io.LineNumberReader) = arm(lnr) { lnr =>
2: var lineNumber = 1
3: var lineText = lnr.readLine()
4: while (null != lineText) {
5: println("%4d: %s" format (lineNumber, lineText))
6: lineNumber += 1
7: lineText = lnr.readLine()
8: }
9: lineNumber
10: } _
res6: Int = 10
Y si vuelvo a intentarlo, me sale esto:
scala> linePrinter(new java.io.LineNumberReader(reader))(noCatch)
java.io.IOException: Stream closed
Ahora supongamos que quiero devolver 0 si ocurre alguna excepción. Puedo hacerlo así:
linePrinter(new java.io.LineNumberReader(reader))(allCatch withApply (_ => 0))
Lo interesante aquí es que desacoplé completamente el manejo de excepciones (la parte de catch
de try
/ catch
) del cierre del recurso , que se realiza finally
. Además, el manejo de errores es un valor que puedo transmitir a la función. Como mínimo, hace que la burla de las declaraciones try
/ catch
/ finally
sea mucho más fácil. :-)
Además, puedo combinar varias Catch
con el método or
, de modo que las diferentes capas de mi código puedan elegir agregar diferentes controladores para las diferentes excepciones. Lo que realmente es mi punto principal, pero no pude encontrar una interfaz rica en excepciones (en el breve tiempo que miré :).
Terminaré con un comentario sobre la definición de arm
que di. No es una buena. Particularmente, no puedo usar métodos de Catch
como toEither
o toOption
para cambiar el resultado de R
a otra cosa, lo que disminuye seriamente el valor de usar Catch
en él. Aunque no estoy seguro de cómo cambiar eso.
Yo diría que es una cuestión de estilo de expresión.
Más allá de ser más corto que su equivalente, el nuevo método catch () ofrece una forma más funcional de expresar el mismo comportamiento. Las declaraciones Try ... catch generalmente se consideran estilo imperativo y las excepciones se consideran efectos secundarios. Catching () coloca una manta en este código imperativo para ocultarlo de la vista.
Más importante aún, ahora que tenemos una función, entonces se puede componer más fácilmente con otras cosas; se puede pasar a otras funciones de orden superior para crear un comportamiento más sofisticado. (No puede pasar una declaración try ... catch con un tipo de excepción parametrizada directamente).
Otra forma de ver esto es si Scala no ofreció esta función catch (). Entonces, lo más probable es que la gente lo "reinvente" de forma independiente y eso causaría la duplicación del código, y llevaría a un código más no estándar. Así que creo que los diseñadores de Scala creen que esta función es lo suficientemente común como para justificar su inclusión en la biblioteca estándar de Scala. (Y estoy de acuerdo)
alex