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 A
s. 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]