Una guía de supervivencia para la mónada Either en Scala

Empecé a trabajar con Scala hace unos meses. Uno de los conceptos que más me costó entender es la Eithermónada. Entonces, decidí jugar con él y comprender mejor su poder.

En esta historia, comparto lo que he aprendido, con la esperanza de ayudar a los programadores a abordar este hermoso idioma.

La mónada Either

Eitheres una de las mónadas más útiles de Scala. Si te estás preguntando qué es una mónada, bueno… ¡No puedo entrar en detalles aquí, tal vez en una historia futura!

Imagínese Eithercomo una caja que contiene un cálculo. Trabaja dentro de esta caja, hasta que decide sacar el resultado de ella.

En este caso específico, nuestra Eithercaja puede tener dos “formas”. Puede ser (ya sea) a Lefto a Right, dependiendo del resultado del cálculo dentro de él.

Puedo oírte preguntar: "Está bien, ¿y para qué es útil?"

La respuesta habitual es: manejo de errores.

Podemos poner un cálculo en el Either, y convertirlo en un Leften caso de errores, o que Rightcontenga un resultado en caso de éxito. El uso de Leftpara errores y Rightpara el éxito es una convención. ¡Entendamos esto con algo de código!

En este fragmento solo estamos definiendo una Eithervariable.

Podemos definirlo como que Rightcontiene un valor válido o como que Leftcontiene un error. También tenemos un cálculo que devuelve an Either, lo que significa que puede ser a Lefto a Right. Simple, ¿no es así?

Proyección derecha e izquierda

Una vez que tengamos el cálculo en el cuadro, es posible que deseemos obtener el valor. Estoy seguro de que espera llamar .geta Eithery extraer su resultado.

Eso no es tan simple.

Piénselo: puso su cálculo en Either, pero no sabe si resultó en a Lefto a Right. Entonces, ¿qué debería .getdevolver una llamada? ¿El error o el valor?

Por eso, para obtener el resultado, debe hacer una suposición sobre el resultado del cálculo.

Aquí es donde entra en juego la proyección .

A partir de un Either, puede obtener un RightProjectiono un LeftProjection. Lo primero significa que asume que el cálculo resultó en a Right, el segundo en a Left.

Lo sé, lo sé ... esto puede ser un poco confuso. Es mejor entenderlo con algo de código. Después de todo, el código siempre dice la verdad .

Eso es. Tenga en cuenta que cuando intenta obtener el resultado de a RightProjection, pero es a Left, obtiene una excepción. Lo mismo ocurre con a LeftProjectiony tienes un Right.

Lo bueno es que puedes mapear en proyecciones. Esto significa que puede decir: "suponga que es un derecho: haga esto con él", Leftsin modificar (y al revés).

De la opción a cualquiera

Option es otra forma habitual de tratar los valores no válidos.

An Optionpuede tener un valor o estar vacío (su valor es Nothing). Apuesto a que notó una similitud con Either… ¡Es incluso mejor, porque en realidad podemos transformar un Optionen un Either! ¡Hora del código!

Es posible transformar un en Optiona Lefto a Right. El lado resultante de Eithercontendrá el valor de Optionsi está definido. Frio. Espera un minuto ... ¿Qué pasa si Optionestá vacío? Obtenemos el otro lado, pero necesitamos especificar lo que esperamos encontrar en él.

De adentro hacia afuera

Eitheres magia, todos estamos de acuerdo en eso. Así que decidimos usarlo para nuestros cálculos inciertos. Un escenario típico cuando se hace programación funcional es el mapeo de una función en un Listde elementos, o en un Map. Hagámoslo con nuestra nueva Eithercomputación con nueva potencia ...

Huston, tenemos un "problema" (bueno, no es un problema GRANDE, pero es un poco incómodo). Sería mejor tener la colección dentro Eitherque muchos Eitherdentro de la colección. Podemos trabajar en eso.

Lista

Empecemos por List. Primero razonamos al respecto, luego podemos jugar con el código.

Tenemos que extraer el valor de Either, ponerlo en Listy poner la lista dentro de un Either. Bien, me gusta.

El punto es que podemos tener a Lefto a Right, por lo que debemos manejar ambos casos. Hasta que encontremos un Right, podemos poner su valor dentro de un nuevo List. Procedemos de esta manera acumulando cada valor en el nuevo List.

Eventualmente llegaremos al final del Listde Either, lo que significa que tenemos un nuevo que Listcontiene todos los valores. Podemos empacarlo Righty listo. Este fue el caso en el que nuestro cálculo no devolvió un Errorinterior a Left.

Si esto sucede, significa que algo salió mal en nuestro cálculo, por lo que podemos devolver el Leftcon el Error. Tenemos la lógica, ahora necesitamos el código.

Mapa

El trabajo Mapes bastante simple una vez que hemos hecho los deberes para List(a pesar de tener que hacerlo genérico):

  • Paso uno: transformar el Mapen una Listde Eithercontiene la tupla (clave, valor).
  • Paso dos: pasa el resultado a la función que definimos List.
  • Paso tres: transforma el Listde tuplas dentro del Eitheren a Map.

Pan comido.

Seamos elegantes: un útil convertidor implícito

We introduced Either and understood it is useful for error handling. We played a bit with projections. We saw how to pass from an Option to an Either. We also implemented some useful functions to “extract” Either from List and Map. So far so good.

I would like to conclude our journey in the Either monad going a little bit further. The utility functions we defined do their jobs, but I feel like something is missing…

It would be amazing to do our conversion directly on the collection. We would have something like myList.toEitherList or myMap.toEitherMap. More or less like what we do with Option.toRight or Option.toLeft.

Good news: we can do it using implicit classes!

Using implicit classes in Scala lets us extend the capabilities of another class.

In our case, we extend the capability of List and Map to automagically “extract” the Either. The implementation of the conversion is the same we defined before. The only difference is that now we make it generic. Isn’t Scala awesome?

Since this can be a useful utility class, I prepared for you a gist you can copy and paste with ease.

object EitherConverter { implicit class EitherList[E, A](le: List[Either[E, A]]){ def toEitherList: Either[E, List[A]] = { def helper(list: List[Either[E, A]], acc: List[A]): Either[E, List[A]] = list match { case Nil => Right(acc) case x::xs => x match { case Left(e) => Left(e) case Right(v) => helper(xs, acc :+ v) } } helper(le, Nil) } } implicit class EitherMap[K, V, E](me: Map[K, Either[E, V]]) { def toEitherMap: Either[E, Map[K, V]] = me.map{ case (k, Right(v)) => Right(k, v) case (_, e) => e }.toList.toEitherList.map(l => l.asInstanceOf[List[(K, V)]].toMap) } }

Conclusion

That’s all folks. I hope this short story may help you to better understand the Either monad.

Please note that my implementation is quite simple. I bet there are more complex and elegant ways to do the same thing. I’m a newbie in Scala and I like to KISS, so I prefer readability over (elegant) complexity.

If you have a better solution, especially for the utility class, I will be happy to see it and learn something new! :-)