2009-10-29 14 views
21

En el próximo Scala 2.8, un paquete util.control se ha añadido que incluye una biblioteca de descanso y una construcción para excepciones de manipulación de modo que el código que se parece a:control de scala 2.8 Excepción: ¿cuál es el punto?

type NFE = NumberFormatException 
val arg = "1" 
val maybeInt = try { Some(arg.toInt) } catch { case e: NFE => None } 

puede ser reemplazado con un código como:

import util.control.Exception._ 
val maybeInt = catching(classOf[NFE]) opt arg.toInt 

Mi pregunta es ¿por qué? ¿Qué agrega esto al idioma que no sea el de proporcionar otra forma (y radicalmente diferente) de expresar lo mismo? ¿Hay algo que se pueda expresar usando el nuevo control pero no a través del try-catch? ¿Se supone que una DSL es hace que el manejo de excepciones en Scala se parezca a algún otro idioma (y si es así, cuál)?

Respuesta

28

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.

+0

* 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 ... –

+0

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. –

+0

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! :-) –

3

Diría que es principalmente 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. Intente ... las declaraciones de catch generalmente se consideran un estilo imperativo y las excepciones se consideran efectos secundarios. Catching() pone una manta en este código imperativo para ocultarlo de la vista.

Más importante aún, ahora que tenemos una función, 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 se puede pasar una instrucció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 conduciría a más código 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

+1

¿Puede señalar a un recurso donde * "las excepciones se consideran efectos secundarios" *? ¿Cómo es una excepción (que no altera necesariamente el estado del objetivo) un efecto secundario –

+0

También, estoy de acuerdo. Claramente, es una cuestión de estilo ... pero tener dos formas completamente diferentes de lograr el mismo resultado es altamente peligroso en mi opinión, ya que dará lugar a problemas de mantenimiento. –

+0

¿Podría dar un ejemplo de un método que toma una expresión * catch * pero que no puede diseñarse para tomar un 'try-catch'? –

9

Como el hombre que lo escribió, las razones son la composición y la encapsulación. El compilador scala (y estoy apostando las bases de origen de mayor tamaño) está plagado de lugares que se tragan todas las excepciones. Puedes verlas con "Capturas en línea" porque el programador es demasiado vago para enumerar las relevantes, lo cual es comprensible. porque es molesto Al permitir definir, reutilizar y componer capturas y finalmente 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 los actores, puede ver ejemplos de enormes bloques try/catch/finally de corte y pegado multiplicados y pegados. Yo estaba/estoy dispuesto a conformarse con try { catch { try { catch { try { catch ...

Mi eventual plan es tener capturas tomar una real PartialFunction en lugar de requerir una lista literal de las declaraciones de casos, lo que representa un nuevo nivel de oportunidad, es decir try foo() catch getPF()

+2

Ah, y olvidé mencionar que mi plan final es capturar una función parcial real en lugar de requerir una lista literal de declaraciones de casos, lo que presenta un nuevo nivel de oportunidad , es decir, prueba foo() catch getPF() – extempore

+0

¿cuál es el estado de tomar una función parcial? –

+2

Implementado hace mucho tiempo, está en scala 2.9.1. – extempore