Además de crear instancias de Left y Right directamente, también podemos importar los métodos de extensión asLeft y asRight desde cats.syntax.either :
import cats.syntax.either._ // for asRight
val a = 3.asRight[String]
// a: Either[String, Int] = Right(3)
val b = 4.asRight[String]
// b: Either[String, Int] = Right(4)
for {
x <- a
y <- b
} yield x*x + y*y
// res3: Either[String, Int] = Right(25)
Estos "constructores inteligentes" tienen ventajas sobre Left.apply y Right.apply porque devuelven resultados de tipo Either en lugar de Left y Right. Esto ayuda a evitar problemas de inferencia de tipo causados por la sobreflecha, como el problema del ejemplo a continuación:
def countPositive(nums: List[Int]) = nums.foldLeft(Right(0)) { (accumulator, num) =>
if(num > 0) {
accumulator.map(_ + 1)
} else {
Left("Negative. Stopping!")
}
}
// error: type mismatch;
//found
//required: scala.util.Right[Nothing,Int]
: scala.util.Either[Nothing,Int]
//accumulator.map(_ + 1)
//^^^^^^^^^^^^^^^^^^^^^^
// error: type mismatch;
//found
//required: scala.util.Right[Nothing,Int]
: scala.util.Left[String,Nothing]
//Left("Negative. Stopping!")
//^^^^^^^^^^^^^^^^^^^^^^^^^^^
Este código falla al compilar por dos razones:
1. el compilador infiere el tipo del acumulador como Right en lugar de Either;
2. no especificamos parámetros de tipo para Right.apply, por lo que el compilador infiere que el parámetro izquierdo es Nothing.
Cambiar a asRight evita ambos problemas. asRight tiene un tipo de retorno de Either, y nos permite especificar completamente el tipo con solo un parámetro de tipo:
def countPositive(nums: List[Int]) = nums.foldLeft(0.asRight[String]) { (accumulator, num) =>
if(num > 0) {
accumulator.map(_ + 1)
} else {
Left("Negative. Stopping!")
}
}
countPositive(List(1, 2, 3))
// res5: Either[String, Int] = Right(3)
countPositive(List(1, -2, 3))
// res6: Either[String, Int] = Left("Negative. Stopping!")
cats.syntax.either agrega algunos métodos de extensión útiles para el objeto complementario Any. Los métodos catchOnly y catchNonFatal son excelentes para capturar excepciones como instancias de:
Either.catchOnly[NumberFormatException]("foo".toInt)
// res7: Either[NumberFormatException, Int] = Left(
// java.lang.NumberFormatException: For input string: "foo"
// )
Either.catchNonFatal(sys.error("Badness"))
// res8: Either[Throwable, Nothing] = Left(java.lang.RuntimeException:
También hay métodos para crear un Either a partir de otros tipos:
Either.fromTry(scala.util.Try("foo".toInt))
// res9: Either[Throwable, Int] = Left(
//
// )
java.lang.NumberFormatException: For input string: "foo"
Either.fromOption[String, Int](None, "Badness")
// res10: Either[String, Int] = Left("Badness")