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.
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.
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 gustaMe 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 gustaMe gusta
[…] se utilizará un Try para intentar obtener el valor del futuro. Ya comentamos este tipo en otro post, pero vamos a ver como aplicaría a los […]
Me gustaMe gusta
[…] will be used to try to get the value of the future. We already talked about this type in another post, but let’s see how this would be applied to […]
Me gustaMe gusta
[…] recordáis el post en el que David habló sobre el tipo Try[T], es un tipo que puede tener dos posibles estados: Success(t: T) o Failure(t: […]
Me gustaMe gusta