domingo, 15 de enero de 2017

Un resumen de Scala for the Impatient, parte 7

Procedimientos. 

Los procedimientos en Scala retornan el tipo Unit y tienen una especial notación sin el =

def box(s : String) { // Look carefully: no =
  val border = "-" * s.length + "--\n"
  println(border + "|" + s + "|\n" + border)
}

Algunas personas no les gusta esta notación por lo que eligen utilizar la misma que las funciones:

def box(s : String): Unit = {
...
}

Lazy Values

Cuando un valor es declarado como lazy el calculo de este valor se realiza cuando se necesita, no antes:

lazy val words = scala.io.Source.fromFile("/usr/share/dict/words").mkString

De esta manera, si words no es utilizado el archivo nunca se abrirá.

Lazy values es útil para retrasar declaraciones de inicialización costosas. También pueden solucionar otros problemas de inicialización, como las dependencias circulares. Además, son esenciales para desarrollar estructuras de datos perezosas como Streams.

val words = scala.io.Source.fromFile("/usr/share/dict/words").mkString
// Se evalúa en el momento que es definido
lazy val words = scala.io.Source.fromFile("/usr/share/dict/words").mkString
// Se evalúa en el momento que es usado
def words = scala.io.Source.fromFile("/usr/share/dict/words").mkString
// Se evalúa cada vez que es usado

Exceptions

Las excepciones funcionan igual que C++ o Java. Cuando se lanza una excepción, por ejemplo:

throw new IllegalArgumentException("x should not be negative")

Se anula la ejecución y el sistema de ejecución busca un manejador de excepciones que puede manejar la excepción IllegalArgumentException. Si esta es manejada se reanuda la ejecución donde es manejado, si no existe un manejador, el programa finaliza. Igual a java.

Como en Java, las excepciones que se lanzan deben ser una subclase de java.lang.Throwable. Sin embargo, a diferencia de Java, Scala no tiene excepciones "verificadas o chequeadas" es decir que nunca se debe declarar una función o un método que lanza una excepción.

Una expresión throw retornan el tipo especial Nothing. Esto es útil en las expresiones if / else. Si una rama tiene el tipo Nothing, el tipo de la expresión if / else es el tipo de la otra rama. Por ejemplo, considere:

if (x >= 0) { sqrt(x)
} else throw new IllegalArgumentException("x should not be negative")

Esta expresión retornara Double, dado que el else retorna Nothing.

La expresión try puede utilizar pattern matching:


try {
process(new URL("http://horstmann.com/fred-tiny.gif"))
} catch {
case _: MalformedURLException => println("Bad URL: " + url)
case ex: IOException => ex.printStackTrace()
}

Cuando no necesitamos las variables podemos utilizar el _ (guión bajo)

La instrucción try / finally le permite disponer de un recurso independientemente de que se haya producido o no una excepción. Por ejemplo:

var in = new URL("http://horstmann.com/fred.gif").openStream()
try {
process(in)
} finally {
in.close()
}

La cláusula finally se ejecuta independientemente de si la función de proceso lanza una excepción. La variable in siempre cerrara el recurso.

Este código es un poco sutil, y plantea varios problemas.

  • ¿Qué pasa si el constructor de URL o el método openStream lanza una excepción? Entonces el bloque try nunca se ingresa, y tampoco es la cláusula finally. Por lo tanto nunca se inicializara. 
  • ¿Por qué no es val in = nueva URL (...). OpenStream () dentro del bloque try? Entonces el alcance de en no se extendería a la cláusula final.
  • ¿Qué pasa si in.close () lanza una excepción? Entonces esa excepción es expulsada de la declaración, reemplazando cualquier anterior. (Esto es igual que en Java, y no es muy agradable.) Idealmente, la antigua excepción permanecería unida a la nueva.

Note que try / catch y try / finally tienen objetivos complementarios. La sentencia try / catch maneja las excepciones, y la sentencia try / finally toma alguna acción (normalmente cleanup) cuando no se maneja una excepción. Es posible combinarlas en una sola instrucción try / catch / finally:

try { ... } catch { ... } finally { ... }