SemigroupK
API Documentation: SemigroupK
SemigroupK has a very similar structure to Semigroup, the difference
is that SemigroupK operates on type constructors of one argument. So, for
example, whereas you can find a Semigroup for types which are fully
specified like Int or List[Int] or Option[Int], you will find
SemigroupK for type constructors like List and Option. These types
are type constructors in that you can think of them as "functions" in
the type space. You can think of the List type as a function which
takes a concrete type, like Int, and returns a concrete type:
List[Int]. This pattern would also be referred to having kind: * ->
*, whereas Int would have kind * and Map would have kind *,* -> *,
and, in fact, the K in SemigroupK stands for Kind.
First some imports.
import cats._
import cats.syntax.all._
For List, the Semigroup instance's combine operation and the SemigroupK
instance's combineK operation are both list concatenation:
SemigroupK[List].combineK(List(1,2,3), List(4,5,6)) == Semigroup[List[Int]].combine(List(1,2,3), List(4,5,6))
// res0: Boolean = true
However for Option, the Semigroup's combine and the SemigroupK's
combineK operation differ. Since Semigroup operates on fully specified
types, a Semigroup[Option[A]] knows the concrete type of A and will use
Semigroup[A].combine to combine the inner As. Consequently,
Semigroup[Option[A]].combine requires an implicit Semigroup[A].
In contrast, SemigroupK[Option] operates on Option where
the inner type is not fully specified and can be anything (i.e. is
"universally quantified"). Thus, we cannot know how to combine
two of them. Therefore, in the case of Option the
SemigroupK[Option].combineK method has no choice but to use the
orElse method of Option:
Semigroup[Option[Int]].combine(Some(1), Some(2))
// res1: Option[Int] = Some(value = 3)
SemigroupK[Option].combineK(Some(1), Some(2))
// res2: Option[Int] = Some(value = 1)
SemigroupK[Option].combineK(Some(1), None)
// res3: Option[Int] = Some(value = 1)
SemigroupK[Option].combineK(None, Some(2))
// res4: Option[Int] = Some(value = 2)
There is inline syntax available for both Semigroup and
SemigroupK. Here we are following the convention from scalaz, that
|+| is the operator from semigroup and that <+> is the operator
from SemigroupK (called Plus in scalaz).
import cats.syntax.all._
val one = Option(1)
val two = Option(2)
val n: Option[Int] = None
Thus.
one |+| two
// res5: Option[Int] = Some(value = 3)
one <+> two
// res6: Option[Int] = Some(value = 1)
n |+| two
// res7: Option[Int] = Some(value = 2)
n <+> two
// res8: Option[Int] = Some(value = 2)
n |+| n
// res9: Option[Int] = None
n <+> n
// res10: Option[Int] = None
two |+| n
// res11: Option[Int] = Some(value = 2)
two <+> n
// res12: Option[Int] = Some(value = 2)
You'll notice that instead of declaring one as Some(1), we chose
Option(1), and we added an explicit type declaration for n. This is
because the SemigroupK type class instances is defined for Option,
not Some or None. If we try to use Some or None, we'll get errors:
Some(1) <+> None
None <+> Some(1)
// error: value <+> is not a member of Some[Int]