A semigroup for some given type A has a single operation
(which we will call combine
), which takes two values of type A, and
returns a value of type A. This operation must be guaranteed to be
associative. That is to say that:
((a combine b) combine c)
must be the same as
(a combine (b combine c))
for all possible values of a,b,c.
There are instances of Semigroup
defined for many types found in the
scala common library. For example, Int
values are combined using addition
by default but multiplication is also associative and forms another Semigroup
.
import cats.Semigroup
Now that you've learned about the Semigroup
instance for Int
try to
guess how it works in the following examples:
import cats.implicits._
Semigroup[Int].combine(1, 2) should be(res0)
Semigroup[List[Int]].combine(List(1, 2, 3), List(4, 5, 6)) should be(res1)
Semigroup[Option[Int]].combine(Option(1), Option(2)) should be(res2)
Semigroup[Option[Int]].combine(Option(1), None) should be(res3)
And now try a slightly more complex combination:
import cats.implicits._
Semigroup[Int => Int].combine(_ + 1, _ * 10).apply(6) should be(res0)
Many of these types have methods defined directly on them,
which allow for such combining, e.g. ++
on List, but the
value of having a Semigroup
type class available is that these
compose, so for instance, we can say
Map("foo" -> Map("bar" -> 5)).combine(Map("foo" -> Map("bar" -> 6), "baz" -> Map()))
Map("foo" -> List(1, 2)).combine(Map("foo" -> List(3, 4), "bar" -> List(42)))
which is far more likely to be useful than
Map("foo" -> Map("bar" -> 5)) ++ Map("foo" -> Map("bar" -> 6), "baz" -> Map())
Map("foo" -> List(1, 2)) ++ Map("foo" -> List(3, 4), "bar" -> List(42))
since the first version uses the Semigroup's combine
operation, it will merge
the map's values with combine
.
import cats.implicits._
val aMap = Map("foo" -> Map("bar" -> 5))
val anotherMap = Map("foo" -> Map("bar" -> 6))
val combinedMap = Semigroup[Map[String, Map[String, Int]]].combine(aMap, anotherMap)
combinedMap.get("foo") should be(Some(res0))
There is inline syntax available for Semigroup
. Here we are
following the convention from scalaz, that |+|
is the
operator from Semigroup
.
You'll notice that instead of declaring one
as Some(1)
, I chose
Option(1)
, and I added an explicit type declaration for n
. This is
because there aren't type class instances for Some or None, but for
Option.
import cats.implicits._
val one: Option[Int] = Option(1)
val two: Option[Int] = Option(2)
val n: Option[Int] = None
one |+| two should be(res0)
n |+| two should be(res1)
n |+| n should be(res2)
two |+| n should be(res3)