Cats の K がつくクラスたち

Cats には末尾が *K* で終わるクラスがいくつかある。通常の型に対する性質や操作を、`F[_]` のような型に当てはめたもので、頻繁に使うものではないが何度か見かけるうちにだんだん気になってくる。

今回は、`SemigroupK`、`MonoidK`、`EitherK`、`InjectK`、`Tuple2K`、`FunctionK` について書いてみる。（Cats は 1.6.0を使った）

## SemigroupK と MonoidK
`SemigroupK` と `MonoidK` は、その名前が示唆するように、おなじみの `Semigroup` と `Monoid` にそれぞれ対応する。

| K なし | K あり |
| ------ | ------ |
|Semigroup[A] は A の二項演算 combineをもつ|SmigroupK[F] は F[A] の二項演算 combineK[A] を持つ。|
|Monoid[A] は Semigroup[A] を継承する。|MonoidK[F] は SemigroupK[F]を継承する。|
|Monoid[A] は 単位元 empty:A を持つ。|MonoidK[F] は 単位元 empty[A]: F[A]を持つ。|

K がつく方では `F[A]` の `A` が何であろうと問われないのが特徴になる。

インスタンスは `List` や `Chain` などが提供されている。たとえば Chain だと、以下のように書ける。

```scala
val mk = implicitly[MonoidK[Chain]]

// <+> は combineK の エイリアス
val ch1: Chain[Symbol] = Chain('a, 'b) <+> mk.empty <+> Chain('c)
// ch1: cats.data.Chain[Symbol] = Chain('a, 'b, 'c)
```

compose を使うと `MonoidK[F]` に `G[?]` を合成して、`Monoid[F[G[?]]]` を作る事ができる。例えば `MonoidK[Chain]` と `IO` を合成すると以下のようになる。

```scala
val composed: MonoidK[λ[α => Chain[IO[α]]]] = mk.compose[IO]

val printIO: Any => IO[Unit] = o => IO { println(o) }
val ch2: Chain[IO[Unit]] = ch1 map printIO

// 明示的に composed の combineK を使う書き方
val ch3: Chain[IO[Unit]] = composed.combineK(ch2, one(printIO("hello")))

// 実は、明示的に combineK しなくても <+> が使える
val ch4: Chain[IO[Unit]] = ch3 <+> one(printIO(100))
```
ch4 を実行すると以下のようになる。

```scala
val io: IO[Chain[Unit]] = ch4.sequence
val result = io.unsafeRunSync() // ここで初めて println が評価される

// 'a
// 'b
// 'c
// hello
// 100
// result = Chain((), (), (), (), ())
```

## EitherK と InjectK

どちらも '[Functional Pearl: Data types a la carte](http://www.staff.science.uu.nl/~swier004/publications/2008-jfp.pdf) (以下、アラカルト論文)' で紹介されてたもの。

`EitherK` は アラカルト論文では下記のような形で定義されていた。

```
data (f :+: g) e = Inl(f e) | Inr(g e)
```

Cats の エンコーディングでは以下のようになっている

```scala
final case class EitherK[F[_], G[_], A](run: Either[F[A], G[A]]) {
  ...
```

例えば以下のような、`_[A]` の形の型があるとき

```scala
case class Ftype[A](a: A)
case class Gtype[A](a: A)
case class Htype[A](a: A)
sealed trait Nil[A]
```
`Ftype :+: Gtype :+: Htype :+: Nil` は以下のように定義できる。

```scala
type FGH[A] = EitherK[Ftype, EitherK[Gtype, EitherK[Htype, Nil, ?], ?], A]
```
次のようにインスタンスを作る。

```scala
val yf: FGH[String] = EitherK.leftc(Ftype("a"))
// EitherK(Left(Ftype(a)))

val yg: FGH[String] = EitherK.rightc(EitherK.leftc(Gtype("a")))
// EitherK(Right(EitherK(Left(Gtype(a)))))

val yh: FGH[String] = EitherK.rightc(EitherK.rightc(EitherK.leftc(Htype("a"))))
// EitherK(Right(EitherK(Right(EitherK(Left(Htype(a)))))))
```

この調子だと右に行くほど面倒になるが、`InjectK` を使えば少し簡潔に書けるようになる。

```scala
val zf: FGH[String] = InjectK[Ftype, FGH].inj(Ftype("a"))
//EitherK(Left(Ftype(a)))

val zg: FGH[String] = InjectK[Gtype, FGH].inj(Gtype("a"))
// EitherK(Right(EitherK(Left(Gtype(a)))))

val zh: FGH[String] = InjectK[Htype, FGH].inj(Htype("a"))
// EitherK(Right(EitherK(Right(EitherK(Left(Htype(a)))))))
```

`FGH[A]` から、特定の`_[A]`を取り出す操作も `InjectK#prj` でできる。

```scala
InjectK[Htype, FGH].prj(yh) // 型が合っている場合
// Some(Htype(a))

InjectK[Ftype, FGH].prj(yh) // 型が違っている場合
// None
```

Cats では [Free Monad](https://typelevel.org/cats/datatypes/freemonad.html) と共に使われる。また例えば、Eff なんかを自前で実装しようと思ったときにもたぶん役立つ（はず）。

## Tuple2K
上の `EitherK` が OR だとすると、AND にあたるのが、この `Tuple2K`。
ドキュメントによれば、論文 '[The Essence of the Iterator Pattern](https://www.cs.ox.ac.uk/jeremy.gibbons/publications/iterator.pdf)' で紹介されたものだというが、`Tuple2K` 自体は以下のように簡単なもの。

```scala
case class Tuple2K[F[_], G[_], A](first: F[A], second: G[A]) {
  ...
```

たとえば次のようにインスタンスが作れる。

```scala
val t1: Tuple2K[Option, List, String] = Tuple2K("0".some, List("1", "2"))
```

Cats では各種の型クラスのインスタンスが提供されていて、例えば `Traverse` なら以下のように使える。

```scala
val t = implicitly[Traverse[λ[α => Tuple2K[Option, List, α]]]]

val r1: Try[Tuple2K[Option, List, Int]] = t.traverse(t1)(s => Try { s.toInt })
// r1 = Success(Tuple2K(Some(0),List(1, 2)))

val t2: Tuple2K[Option, List, String] = Tuple2K("0".some, List("1", "a"))
val r2: Try[Tuple2K[Option, List, Int]] = t.traverse(t2)(s => Try { s.toInt })
// r2 = Failure(java.lang.NumberFormatException: For input string: "a")
```

## FunctionK
`F[_]`から `G[_]` への変換をあらわすクラス。`F ~> G` と書くことができる。

API ドキュメンテーションに載っているのは、`List` から `Option` への変換で下のように使える。

```scala
val listToOption: List ~> Option = λ[List ~> Option](_.headOption)

listToOption.apply(List(1, 2, 3)) // Some(1)
listToOption.apply(List())        // None
```
`F` と `G` が関手で、さらにその他もろもろの条件を満たせば自然変換になるが、特に関手でなくてはならないという制約はない。例えば下記のような型にも適用することもできる。

```scala
case class Ftype[A](a: A)
case class Gtype[A](a: A)
case class Htype[A](a: A)
```

これらの型の間の `FunctionK` を以下のように用意しておいた上で、その他の演算を試してみる。

```scala
val f2g: Ftype ~> Gtype = λ[Ftype ~> Gtype] { case Ftype(a) => Gtype(a) }
val f2h: Ftype ~> Htype = λ[Ftype ~> Htype] { case Ftype(a) => Htype(a) }
val g2h: Gtype ~> Htype = λ[Gtype ~> Htype] { case Gtype(a) => Htype(a) }
```

`Semigroup` と `SemigroupK`、あるいは `MonoidK` と `MonoidK` と同様の関係が、`Function1` と `FunctionK` との間にも成り立っていて、`andThen` や `compose` を使って合成することができる。

```scala
(f2g andThen g2h)(Ftype(100)) // Htype(100)
(g2h compose f2g)(Ftype(101)) // Htype(101)
```
さらに、上で説明した、`EitherK` と `Tuple2K` に関連して、`or` と `and` も提供されていて下記のように使える。

```scala
// or
(f2h or g2h)(EitherK.leftc(Ftype(102))) // Htype(102)
(f2h or g2h)(EitherK.rightc(Gtype(103))) // Htype(103)

// and
(f2g and f2h)(Ftype(104)) // Tuple2K(Gtype(104), Htype(104))
```

## 補足
[以前の記事](https://qiita.com/yasuabe2613/items/4202dd51469e5e56ddd1)に書いた [Iota](https://github.com/frees-io/iota) というライブラリで、入れ子になった EitherK をより簡単に使うための `CopK` というものが提供されている。