Using cats.Semigroup one can write this:
import cats.Semigroup
import cats.implicits._
val l1: String Either Int = Left("error")
val r1: String Either Int = Right(1)
val r2: String Either Int = Right(2)
l1 |+| r1 // Left("error")
r1 |+| r2 // Right(3)
I would like to have an equally idiomatic operator (combine-like) that works like this:
Right
in my computation, return a Right
Left
s, return a Left
E.g.:
Right(1) |+| Right(2) // Right(3)
Right(1) |+| Left("2") // Right(1)
Left("1") |+| Left("2") // Left("12") // in my particular case the wrapped value here does not really matter (could also be e.g. Left("1") or Left("2")), but I guess Left("12") would be the must logical result
Is there something like this already defined in e.g. cats on Either
?
The ‘Semigroup’ typeclass in Haskell is an abstraction that serves to provide the semigroup algebraic structure for types that implement it. Any type ‘a’ can implement the Semigroup typeclass, providing that the type has an associative binary operation of the type: The binary function needs only to be associative, not commutative.
In abstract algebra, a semigroup is a set together with a binary operation. For set, in Haskell, you can more or less substitute the word type; there are ways in which types do not perfectly correspond to sets, but it is close enough for this purpose. A binary operation is a function that takes two arguments.
Example: Consider a semigroup (N, +), where N is the set of all natural numbers and + is an addition operation. The algebraic system (E, +) is a subsemigroup of (N, +), where E is a set of +ve even integers. Consider a non empty set A = {a 1 ,a 2 ,.....a n }. Now, A* is the set of all finite sequences of elements of A, i.e.,
Hence, the algebraic system (A, *), is a semigroup. Consider a semigroup (A, *) and let B ⊆ A. Then the system (B, *) is called a subsemigroup if the set B is closed under the operation *. Example: Consider a semigroup (N, +), where N is the set of all natural numbers and + is an addition operation.
There are a bunch of lawful semigroup instances for Either
, and which of them should be included in Cats was a matter of some debate. Cats, Scalaz, and Haskell all make different choices in this respect, and the instance you're describing (flipped but with both lefts and right combining) is different from all three of those, it doesn't have a specific name that I'm aware of, and it isn't provided under any name or in any form by Cats.
That's of course not a problem in itself, since as we'll see below it's pretty easy to verify that this instance is lawful, but there is one potential issue you should be aware of. You don't really explain your intended semantics, but if you ever want to promote this to a Monoid
, the fact that you pick the Right
when you have both a Left
and a Right
means that your zero will have to be Left
. This might be kind of weird if you're thinking of rights as successes and lefts as errors that are safe to ignore when combining values.
You're asking about Semigroup
, though, not Monoid
, so let's just ignore that for now and show that this thing is lawful. First for the definition:
import cats.kernel.Semigroup
implicit def eitherSemigroup[A, B](implicit
A: Semigroup[A],
B: Semigroup[B]
): Semigroup[Either[A, B]] = Semigroup.instance {
case (Right(x), Right(y)) => Right(B.combine(x, y))
case (r @ Right(_), Left(_)) => r
case (Left(_), r @ Right(_)) => r
case (Left(x), Left(y)) => Left(A.combine(x, y))
}
And then the checking part:
import cats.instances.int._
import cats.instances.string._
import cats.kernel.instances.either.catsStdEqForEither
import cats.kernel.laws.discipline.SemigroupTests
import org.scalacheck.Test.Parameters
SemigroupTests(eitherSemigroup[String, Int]).semigroup.all.check(Parameters.default)
And yeah, it's fine:
+ semigroup.associative: OK, passed 100 tests.
+ semigroup.combineAllOption: OK, passed 100 tests.
+ semigroup.repeat1: OK, passed 100 tests.
+ semigroup.repeat2: OK, passed 100 tests.
Personally if I wanted something like this I'd probably use a wrapper to avoid confusing future readers of my code (including myself), but given that nobody really knows what the semigroup of Either
should do, I don't think using a custom instance is as big of a problem as it is for most other types from the standard library.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With