SemigroupK 

4日目に出てきた Semigroup は関数型プログラミングの定番で、色んな所に出てくる。

scala> import cats._, cats.data._, cats.implicits._
import cats._
import cats.data._
import cats.implicits._

scala> List(1, 2, 3) |+| List(4, 5, 6)
res0: List[Int] = List(1, 2, 3, 4, 5, 6)

scala> "one" |+| "two"
res1: String = onetwo

似たもので SemigroupK という型コンストラクタ F[_] のための型クラスがある。

@typeclass trait SemigroupK[F[_]] { self =>

  /**
   * Combine two F[A] values.
   */
  @simulacrum.op("<+>", alias = true)
  def combineK[A](x: F[A], y: F[A]): F[A]

  /**
   * Given a type A, create a concrete Semigroup[F[A]].
   */
  def algebra[A]: Semigroup[F[A]] =
    new Semigroup[F[A]] {
      def combine(x: F[A], y: F[A]): F[A] = self.combineK(x, y)
    }
}

これは combineK 演算子とシンボルを使ったエイリアスである <+> をを可能とする。使ってみる。

scala> List(1, 2, 3) <+> List(4, 5, 6)
res2: List[Int] = List(1, 2, 3, 4, 5, 6)

Semigroup と違って、SemigroupKF[_] の型パラメータに何が入っていても大丈夫だ。

SemigroupK としての Option 

Option[A] は型パラメータ ASemigroup である時に限って Option[A]Semigroup を形成する。

scala> case class Foo(x: String)
defined class Foo

そのため、これはうまくいかない:

scala> Foo("x").some |+| Foo("y").some
<console>:33: error: value |+| is not a member of Option[Foo]
       Foo("x").some |+| Foo("y").some
                     ^

だけど、これは大丈夫:

scala> Foo("x").some <+> Foo("y").some
res3: Option[Foo] = Some(Foo(x))

この 2つの型クラスの振る舞いは微妙に異なるので注意が必要だ。

scala> 1.some |+| 2.some
res4: Option[Int] = Some(3)

scala> 1.some <+> 2.some
res5: Option[Int] = Some(1)

SemigroupOption の中身の値もつなげるが、SemigroupK の方は最初の選択する。

SemigroupK 則 

trait SemigroupKLaws[F[_]] {
  implicit def F: SemigroupK[F]

  def semigroupKAssociative[A](a: F[A], b: F[A], c: F[A]): IsEq[F[A]] =
    F.combineK(F.combineK(a, b), c) <-> F.combineK(a, F.combineK(b, c))
}

Contents