Semigroupal no siempre proporciona el comportamiento que esperamos, particularmente para los tipos que también tienen instancias de Monad. Hemos visto el comportamiento del Semigroupal para Option. Veamos algunos ejemplos para otros tipos.
La semántica de Future proporciona ejecución paralela en lugar de secuencial:
import cats.Semigroupal
import cats.instances.future._ // for Semigroupal
import scala.concurrent._
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global
val futurePair = Semigroupal[Future].
product(Future("Hello"), Future(123))
Await.result(futurePair, 1.second)
// res0: (String, Int) = ("Hello", 123)
Los dos futuros comienzan a ejecutarse en el momento en que los creamos, por lo que ya están calculando los resultados en el momento en que llamamos producto. Podemos usar la sintaxis de aplicación para comprimir números fijos de futuros:
import cats.syntax.apply._ // for mapN
case class Cat(
name: String,
yearOfBirth: Int,
favoriteFoods: List[String]
)
val futureCat = (
Future("Garfield"),
Future(1978),
Future(List("Lasagne"))
).mapN(Cat.apply)
Await.result(futureCat, 1.second)
// res1: Cat = Cat("Garfield", 1978, List("Lasagne"))
La combinación de Listas con Semigroupal produce algunos resultados potencialmente inesperados. Podríamos esperar un código como el siguiente para comprimir las listas, pero en realidad obtenemos el producto cartesiano de sus elementos:
import cats.Semigroupal
import cats.instances.list._ // for Semigroupal
Semigroupal[List].product(List(1, 2), List(3, 4))
// res2: List[(Int, Int)] = List((1, 3), (1, 4), (2, 3), (2, 4))
Esto es quizás sorprendente. Comprimir listas tiende a ser una operación más común.
Podríamos esperar que el producto aplicado a O acumule errores en lugar de fallar rápidamente. Nuevamente, tal vez sorprendentemente, encontramos que el producto implementa el mismo comportamiento de falla rápida que flatMap:
import cats.instances.either._ // for Semigroupal
type ErrorOr[A] = Either[Vector[String], A]
Semigroupal[ErrorOr].product(
Left(Vector("Error 1")),
Left(Vector("Error 2"))
)
// res3: ErrorOr[Tuple2[Nothing, Nothing]] = Left(Vector("Error 1"))
En este ejemplo, el producto ve la primera falla y se detiene, aunque es posible examinar el segundo parámetro y ver que también es una falla.