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 considerarlos como datos: una excepción es una información sobre la ejecución del programa, que luego puede usarse como entrada para otras partes del programa.
El paradigma try
/catch
utilizado 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 comienzan a complicarse. O bien tiene que duplicar el código por completo, o puede rodar su propia biblioteca para manejarlo.
Por lo tanto, en un lenguaje que pretende admitir tanto el estilo funcional como el de OO, no debería sorprendernos ver el soporte de la biblioteca para tratar las excepciones como datos.
Y tenga en cuenta que hay muchísimas otras posibilidades proporcionadas por Exception
para manejar cosas. Puede, por ejemplo, manipuladores de captura de cadena, de forma muy parecida a como funciona la cadena de elevación para facilitar la delegación de responsabilidad sobre el manejo de solicitudes de página web.
Aquí es un ejemplo de lo que puede hacerse, ya que la gestión automática de recursos está en boga 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)
)
que le da un cierre seguro del recurso (nótese el uso de ignorar), y todavía aplica cualquier lógica de captura que quieras usar.
(*) Curiosamente, el control de excepción de Forth, catch
& throw
, es una mezcla de ellos. El flujo salta de throw
a catch
, pero esa información se trata como datos.
EDITAR
Ok, ok, cedo. Daré un ejemplo. UN solo ejemplo, y eso es todo! Espero que esto no sea demasiado artificial, pero no hay forma de evitarlo. Este tipo de cosas sería más útil en marcos grandes, no en muestras pequeñas.
En cualquier caso, primero definamos algo que ver con el recurso. Me decidí por la impresión de 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í es el tipo de esta función:
linePrinter: (lnr: java.io.LineNumberReader)(util.control.Exception.Catch[Int]) => Int
Así, arm
recibieron una que se pueda cerrar genérico, pero Necesito un LineNumberReader, así que cuando llamo a esta función, necesito pasar eso. Lo que devuelvo, sin embargo, es una función Catch[Int] => Int
, lo que significa que necesito pasar dos parámetros al linePrinter
para que funcione. Vamos a llegar a 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))
Así que, ahora, toca aplicarla. En primer lugar, un ejemplo sencillo:
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 lo intento de nuevo, 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 de esta manera:
linePrinter(new java.io.LineNumberReader(reader))(allCatch withApply (_ => 0))
Lo interesante aquí es que I desacoplado por completo el manejo de (la parte catch
de try
/catch
) a partir del cierre del recurso, que se realiza a través finally
excepción . Además, el manejo de errores es un valor que puedo transmitir a la función. Por lo menos, hace burla de try
/catch
/finally
declaraciones mucho más fáciles. :-)
Además, puedo combinar múltiples Catch
usando el método or
, de modo que diferentes capas de mi código puedan elegir agregar diferentes controladores para diferentes excepciones. 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 bueno.Particularmente, no puedo usar los métodos 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.
* Daniel * - una respuesta reflexiva como siempre, pero ¿podría ampliar la respuesta para proporcionar 2 ejemplos diferentes de llamar a su método de "brazo"? Veo que puede suministrar diferentes manejadores, pero luego puede hacerlo en el estilo imperativo al detectar las excepciones en el sitio de llamadas ... –
Todos los idiomas de turing-complete son equivalentes. Puede hacer "filtro" sin funciones de orden superior, es simplemente más incómodo. Trataré de pensar en un buen ejemplo para ilustrar cómo esto es diferente. –
Como nota al margen, evité escribir un ejemplo en primer lugar porque las API de Java que implementan Cerrar son horribles. ¡No quería pasar por el dolor! :-) –