Translate

lunes, 6 de junio de 2022

Mónadas en Cats parte 10

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")