Capturando excepciones en Scala: el tipo Try

Es bastante típico llamar a algunas funciones que pueden lanzar excepciones. Por ejemplo, cuando hacemos una llamada a un servicio web, puede que exista un error de conexión que lance una excepción.

En un primer momento, lo que se nos ocurre es utilizar un bloque try-catch para poder capturar el error en caso de que la función explote:

try {
  val userInfo = myWebServer.getUserInfo("Royston")
  userInfo.email
} catch {
  case e: ConnectionException => // do something
  case e: BadRequestException => // do something
  case _ => //do something
}

Esto está muy bien porque, si salta alguna excepción, podremos capturarla y hacer algo con ella. Sin embargo, utilizar el bloque try no es un enfoque muy funcional. No nos proporciona muchas opciones de transformación y tampoco nos permite dejar que la acción a realizar en caso de que salte alguna excepción se pueda decidir en un momento posterior.

Tipo Try al rescate

El tipo Try es un tipo que dada una acción a realizar puede devolver bien un Success con el resultado de dicha acción, o bien una excepción producida al intentar ejecutarla encapsulada en un Failure.

import scala.util.{ Try, Success, Failure }

Try(myWebServer.getUserInfo("Royston")) match {
  case Success(userInfo) => 
    userInfo.email
  case Failure(exception: ConnectionException) => 
    //do something
  case Failure(exception: BadRequestException) => 
    //do something
  case Failure(exception) => 
    //do something
}

Como se puede observar, utilizando el pattern matching, podemos definir que hacer en función del resultado de evaluar la acción. Ahora, por ejemplo, podemos utilizar este tipo en la signatura de una función:

def getUserInfo(username: String): Try[String] =
  Try(myWebServer.getUserInfo("Royston"))

De esta forma podemos dejar que otra parte del código decida que hacer con el resultado de la función.

Otra ventaja frente a usar un bloque de código try-catch, es que podemos utilizar los métodos que nos ofrece la api del tipo Try. Estos métodos nos permitiran evaular o modificar el valor contenido en el Try. Por ejemplo, podemos usar el método map:

def encrypt(value: String): String =
  value.map(_.toInt).mkString("-")

getUserInfo("Royston").map(_.email).map(_.encrypt)

En el anterior código lo que estamos haciendo es codificar el email en caso de que se obtenga satisfactoriamente. En caso de que se produzca alguna excepción, no se realizará ninguna transformación. El comportamiento es idéntico al que vimos cuando se aplicaba al tipo Option.

Además, debido a que contiene un método flatmap podemos utilizarlo en for comprehension y combinar varias instancias del tipo Try.

def getEmail(username: String): Try[String] =
  getUserInfo(username).map(_.email)

def getAge(username: String): Try[Int] =
  getUserInfo(username).map(_.age)

for {
  email <- getEmail("Royston")
  age <- getAge("Royston")
  if age > 18
} yield s"User with $email is not under 18"

En este caso, si alguna de las llamadas falla, o el usuario es menor de edad, el flujo de ejecución parará, y no se realizará ninguna transformación En caso contrario, el Try se transformará.

Mónadas, mónadas everywhere

No estaría bien acabar este post sin que nos explote un poco la cabeza. Hemos podido ver que usando el tipo Try podemos conseguir que nuestro programa sea más funcional. Gracias a que es una mónada cumple algunas de las leyes monádicas (ein?) podemos combinarlos entre ellos para que nuestros programas sean más expresivos.

56248419

Pero bueno, no hay que asustarse demasiado. Más adelante ahondaremos  en las mónadas y otras abstracciones funcionales y veremos que no es tan complicado.

5 comentarios en “Capturando excepciones en Scala: el tipo Try

  1. Muy buen post, gracias por vuestro trabajo. Sólo añadir un ligero apunte: teniendo en cuenta las leyes monádicas, Try no cumple la segunda ley de igualdad a izquierdas, unit(x).flatMap(f) == f(x), para todo x ya que la función f(x) podría lanzar una excepción no capturada en la parte derecha de la igualdad y en la izquierda dar como resultado un Faliure(e).

    Aún así se puede usar como cualquier mónada en los for comprehension de Scala y se puede componer con otros constructores.

    Corregidme si me equivoco 😉 y gracias por vuestro trabajo divulgando Scala y la programación funcional en español.

    Saludos.

    Me gusta

    • Muchas gracias por tu comentario. Está genial recibir feedback para poder mejorar 🙂

      Tienes toda la razón. Quizás sea más correcto decir que cumple algunas de las leyes monádicas.
      Si a alguien le interesa, he encontrado este enlace donde se trata este tema con algo más de detalle: https://gist.github.com/ms-tg/6222775

      Muchas gracias de nuevo!
      Saludos!

      Me gusta

Replica a dvallejonava Cancelar la respuesta