El poder de Either en Scala
Hace tiempo escribí acerca de la versatilidad que nos da el usar Option en Scala, cuando se puede manejar un valor que podría ser nulo.
Esto es muy útil por ejemplo para un método de login: pasamos usuario y password, y obtenemos un Usuario, siempre y cuando exista el usuario con ese nombre y su password sea correcto. Entonces podemos implementar el método de estar forma:
Entonces cuando invocamos el método login
, ya no tenemos que validar contra null en un if
para proceder de una forma, y presentar un error en el else
; en vez de eso podemos hacer distintas funciones:
Casos más sofisticados
Supongamos que queremos manejar errores de manera más específica. Internamente, el login pudo fallar por distintas razones: el usuario no existe, o el password es inválido, o el status del usuario es inactivo, ha sido bloqueado por alguna razón, etc.
Tal vez hacia afuera, en una aplicación web por ejemplo, solamente queremos presentar condiciones de éxito o error, y es suficiente con un Option, pero si internamente queremos tomar distintas acciones dependiendo del error que haya ocurrido, entonces usar un Option
ya no es suficiente. Afortunadamente, tenemos otra clase similar, llamada Either
, la cual puede contener dos objetos completamente distintos. Entonces supongamos que implementamos una clase ErrorLogin
que indica la razón por la que falló, y reimplementamos nuestro método así:
val user = //Buscamos el usuario en la base de datos
if (user == null) {
Right(LoginError(UsuarioNoExiste))
} else if (!passwordEsValido) {
Right(LoginError(PasswordInvalido))
} else if (user.status != 1) {
Right(LoginError(StatusInvalido, "Status es " + user.status))
} else {
Left(user)
}
}
Either
es una clase abstracta, igual que Option
. Y así como Option tiene dos encarnaciones, Some
y None
, Either
tiene también dos versiones concretas: Left
y Right
.
Todo esto sirve para expresar, en este caso, que el método login
puede devolver un Usuario, o un LoginError. Y entonces podemos actuar de distintas formas dependiendo del resultado del login. Una forma muy simple sería así:
Pero ese código no aprovecha realmente las ventajas de tener un Either
. Así como con Option
podemos usar map, orElse, getOrElse, etc, podemos también aprovechar algunas funciones que implementa Either
para expresar de manera más clara nuestro propósito. En este caso, hay un método llamado fold
que recibe dos funciones: la primera función debe recibir un parámetro del tipo especificado para el Left
y la segunda función debe recibir un parámetro del tipo especificado para el Right
. Solamente una de las funciones será invocada, dependiendo del contenido real del Either
. La única restricción es que ambas funciones deben tener el mismo tipo de retorno. En este caso, nuestroas métodos continuar
y error
podrían devolver un URL por ejemplo, para saber a dónde redirigir al usuario según el resultado del login:
def error(err:LoginError)=//blabla
val login = login("usuario", "password")
val url = login.fold(continuar, error)
Aquí estamos aprovechando la característica call-by-name que nos permite simplemente pasar el nombre de un método o función como parámetro; dado que el método continuar
recibe un Usurio y el método error
recibe un LoginError, podemos usar únicamente sus nombres como parámetros; es importante recordar que en esa última línea no estamos invocando a ninguno de los dos métodos, sino que se los pasamos a fold
y ahí se ejecutará uno de ellos nada más.
Una vez que se comprende bien Either
, se puede utilizar en este tipo de métodos que pueden devolver dos valores distintos dependiendo del éxito o fracaso de una operación, en donde un Option
no es lo suficientemente expresivo porque solamente puede contener un valor o no contener nada.
- ezamudio's blog
- Inicie sesión o regístrese para enviar comentarios
Comentarios
Otros ejemplos sencillos de Either
Contribuyo al post de Enrique con el siguiente ejemplo de Either disponible en varios lugares:
Definimos una función que al ejecutar un bloque de código devolverá Right (result) si el bloque se ejecuta con éxito o Left(throwable) si el bloque dispara un Throwable.
try {
Right(block)
} catch {
case ex => Left(ex)
}
Usamos ahora la función para ambos casos: que el bloque se ejecute con éxito o que falle disparando un Throwable:
throwableToLeft { s.toUpperCase } match {
case Right(s) => println(s)
case Left(e) => e.printStackTrace
}
// prints "HELLO"
s = null
throwableToLeft { s.toUpperCase } match {
case Right(s) => println(s)
case Left(e) => e.printStackTrace
}
// prints NullPointerException stack trace
Left y Right son clases cases, definidas en el mismo archivo que la clase Either que es selllada (sealed) para poder determinar todos sus posibles cases.
cierto
También se puede hacer pattern matching con Right y Left. Y de hecho la convención es alrevés de mi ejemplo: Left suele usarse para devolver un error y Right para el resultado exitoso. Será que soy zurdo, y me pasa como cuando quiero acomodar los cubiertos en una mesa y a veces los pongo bien, a veces alrevés porque le pienso demasiado y hago memoria de cómo es para los diestros y lo quiero invertir pero a veces me acuerdo mal, etc.
Yo también soy zurdo
Pues ya somos dos, y que recuerden los diestros, que la chingonería está de parte nuestra por el trabajo que nos hacen pasar en este mundo de derechos.
Pues ya somos muchos zurdos
Pues ya somos muchos zurdos no? Yo hasta la fecha no puedo decir sin tomar una llave ( o grifo ) para donde debe de girar, me pasa que a veces estoy apretando una tuerca cuando quiero zafarla. En fin, había estado queriendo leer este artículo y ahora tengo una razón más
¿Habrá otra forma de no tener
¿Habrá otra forma de no tener que preguntar si es left o right? Una cosa que entendi y me agradó de
Option
es que evitar tener que preguntar si esSome
orNone
y simplemente se usa el valor y ya sabiendo que no va a fallar ( meeeh.. algo similar al NullObjectPattern en OO , pero.. no .. ) y entonces, se puede escapar de NullLand ( para más sobre Option, leer las respuestas de aquí y acá y claro acá). Hasta ahí todo bien.Acá con
Either
yo esperaba que fuera algo similar, y de hecho lo es, pero en los ejemplos puestos se muestra o pattern matching o un if, debe de haber una forma de no hacer esto y dejar que el código fluya dependiendo de los dos valores, como con los encadenamientosmap, flatMap, orElse
o for comprehensions.Lo que es muy valioso es poder expresar un valor como uno de dos posibles valores, ahí ya es seguro que una cosa es Either left or right :)
El uso de Either es "limitado"
Scala define la clase Either como sigue:
Un objeto de clase Either respresenta un valor de uno de dos tipos posibles para implementar la operación de "union disjunta", que no es otra cosa que la operación de unión modificada para indexar elementos de conjuntos disjuntos.
La clase Either es extendida por dos clases case: Left y Right que se usan como constructores datos y representan los dos valores posibles de un objeto Either.
El caso de uso típico de Either es una alternativa a la clase Option y por convención Left debe representar un fallo y Right debe representar algo similar a Some.
De las cosas que he leído sobre Either, hay algo en común y es que casi todo el mundo coincide en que su uso básico es el de los ejemplos que aquí se han descrito, usando por supuesto las ventajas de pattern matching para discriminar un resultado exitoso de un resultado fallido, y sobre esa base tomar acciones. Para ese propósito es muy útil.
De los libros que he consultado de Scala, solamente uno hace mención de la clase Either y el ejemplo es similar.
sin if
Oscar, lee el articulo completo. Al final muestro la manera de usarlo sin if, similar a como usas map/flatMap con Option, usas
fold
con el Either.Y para apretar/aflojar tuercas y tornillos, abrir y cerrar llaves, piensa en el sentido de las manecillas del reloj, tu que todavia conociste los relojes analogicos con manecillas. En el sentido de las manecillas, es cerrar/apretar y en el sentido opuesto es abrir/aflojar.
Cuidado con los tanques de gas LP @OscarRyz
Pero no vayas a seguir el consejo de Enrique con algunos contenedores de gas LP, pues ellos tienen cuerda inversa o rosca izquierda como se dice en casi todos los países de habla hispana.
jeje presisamente, leí el
jeje presisamente, leí el comentario de Enrique y me acorde del contenedor de gas, intente aplicar su consejo, pero no cuadra, pues ahí es al revez, como dice el Dr. Ferro.
Saludos.