La razón de los resultados sorprendentes de List y de Either es que ambas son mónadas. Si tenemos una mónada, podemos implementar el producto de la siguiente manera.
import cats.Monad
import cats.syntax.functor._ // for map
import cats.syntax.flatMap._ // for flatmap
def product[F[_]: Monad, A, B](fa: F[A], fb: F[B]): F[(A,B)] =
fa.flatMap(a =>
fb.map(b =>
(a, b)
)
)
Sería muy extraño si tuviéramos diferentes semánticas para el producto dependiendo de cómo lo implementemos. Para garantizar una semántica coherente, Cats' Monad (que amplía Semigroupal) proporciona una definición estándar de producto en términos de map y flatmap, como mostramos anteriormente.
Incluso nuestros resultados para Future son un truco de la luz. flatMap proporciona un orden secuencial, por lo que el producto proporciona lo mismo. La ejecución paralela que observamos ocurre porque nuestros futuros constituyentes comienzan a correr antes de que llamemos al producto. Esto es equivalente al patrón clásico create-then-flatMap:
val a = Future("Future 1")
val b = Future("Future 2")
for {
x <- a
y <- b
} yield (x, y)
Entonces, ¿por qué molestarse con Semigroupal? La respuesta es que podemos crear tipos de datos útiles que tengan instancias de Semigroupal (y Applicative) pero no Monad. Esto nos libera para implementar el producto de diferentes maneras.