Semigroup

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:

scala> import cats._
import cats._

scala> import cats.std.all._
import cats.std.all._

scala> Semigroup[Int].combine(1, 2)
res0: Int = 3

scala> Semigroup[List[Int]].combine(List(1,2,3), List(4,5,6))
res1: List[Int] = List(1, 2, 3, 4, 5, 6)

scala> Semigroup[Option[Int]].combine(Option(1), Option(2))
res2: Option[Int] = Some(3)

scala> Semigroup[Option[Int]].combine(Option(1), None)
res3: Option[Int] = Some(1)

scala> Semigroup[Int => Int].combine({(x: Int) => x + 1},{(x: Int) => x * 10}).apply(6)
res4: Int = 67

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 typeclass available is that these compose, so for instance, we can say

scala> import cats.implicits._
import cats.implicits._

scala> Map("foo" -> Map("bar" -> 5)).combine(Map("foo" -> Map("bar" -> 6), "baz" -> Map()))
res5: Map[String,scala.collection.immutable.Map[String,Int]] = Map(baz -> Map(), foo -> Map(bar -> 11))

scala> Map("foo" -> List(1, 2)).combine(Map("foo" -> List(3,4), "bar" -> List(42)))
res6: Map[String,List[Int]] = Map(foo -> List(1, 2, 3, 4), bar -> List(42))

which is far more likely to be useful than

scala> Map("foo" -> Map("bar" -> 5)) ++  Map("foo" -> Map("bar" -> 6), "baz" -> Map())
res7: scala.collection.immutable.Map[String,scala.collection.immutable.Map[_ <: String, Int]] = Map(foo -> Map(bar -> 6), baz -> Map())

scala> Map("foo" -> List(1, 2)) ++ Map("foo" -> List(3,4), "bar" -> List(42))
res8: scala.collection.immutable.Map[String,List[Int]] = Map(foo -> List(3, 4), bar -> List(42))

There is inline syntax available for Semigroup. Here we are following the convention from scalaz, that|+| is the operator from Semigroup.

scala> import cats.syntax.all._
import cats.syntax.all._

scala> import cats.implicits._
import cats.implicits._

scala> import cats.std._
import cats.std._

scala> val one = Option(1)
one: Option[Int] = Some(1)

scala> val two = Option(2)
two: Option[Int] = Some(2)

scala> val n: Option[Int] = None
n: Option[Int] = None

scala> one |+| two
res9: Option[Int] = Some(3)

scala> n |+| two
res10: Option[Int] = Some(2)

scala> n |+| n
res11: Option[Int] = None

scala> two |+| n
res12: Option[Int] = Some(2)

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 typeclass instances for Some or None, but for Option. If we try to use Some and None, we'll get errors:

scala> Some(1) |+| None
<console>:30: error: value |+| is not a member of Some[Int]
       Some(1) |+| None
               ^

scala> None |+| Some(1)
<console>:30: error: value |+| is not a member of object None
       None |+| Some(1)
            ^

N.B. Cats does not define a Semigroup typeclass itself, it uses the Semigroup trait which is defined in the algebra project on which it depends. The cats package object defines type aliases to the Semigroup from algebra, so that you can import cats.Semigroup.

useful links