Scala Cats 学習メモ - 0日目 -

# 背景

実務に入って5ヶ月目、そろそろ放置していた cats や shapeless についてきちんと学ばないとなー、という気持ちが出てきたので、取り組んでみる。
まずは cats から。

# 学習教材

Scala with cats 、と言いたいところですが、まずは手頃なところから手をつけたい・・・。
ということで、cats 公式 HP に記載されているチュートリアルページの日本語版を見つけたので、そちらから手をつけることにする。

https://typelevel.org/cats/resources_for_learners.html

https://eed3si9n.com/herding-cats/ja/

# 学習メモ

## 0 日目

Cats は、主に 2 つの部分から構成される。

- 新しいデータ型 (Validated, State, etc)
- 実用上必要な多くの汎用関数の実装 (アドホック多相性、 trait + implicit)

### 多相性って何？

#### パラメータ多相 (parametric polymorphism)

```scala
def head[A](xs: List[A]): A = xs(0)
```

上記の関数 `head` は:

- `A` のリストを引数に取り
- `A` を返す

`A` は何でも良い。( `Int` , `String` , ...)
どの `A` でも動作し、存在できるすべての `A` に対して定義されている。

```scala
// Int の例
head(1 :: 2 :: Nil)
// res1: Int = 1
```

```scala
// ケースクラス Car の例
case class Car(make: String)

head(Car("GT-R") :: Car("Fairlady-Z") :: Nil)
// res2: Car = Car(make = "GT-R")
```


> パラメータ多相 (parametric polymorphism) とは、
> ある値の型が 1 つもしくは複数の（制限のない） **型変数** を含むことを指す。
> その値は型変数を具象型によって置換することにより得られる型なら、どれでも採用できる

https://wiki.haskell.org/Polymorphism

#### 派生型による多態 (subtype polymorphism)

ここで、型 `A` の値を 2 つ引数に取り、それらを足す関数 `plus` について考えてみる。

```scala
def plus[A](a1: A, a2: A): A = ???
```

型 `A` の中身ごとに、"足す" 定義を分ける必要がある。
これを実現する方法の 1 つが派生型 (subtyping) だ。

```scala
trait PlusIntf[A] {
  def plus(a2: A): A
}

def plusBySubtype[A <: PlusIntf[A]](a: A, a2: A): A = a1.plus(a2)
```

上記の定義で、 型 `A` の中身ごとに関数 `plus` を定義できるようになった。
しかし、この方法はデータ型の定義時に `Plus` を mixin する必要があるため、柔軟性に欠ける。

話は逸れるが、 [A <: PlusIntf[A]] について説明。
（自分用メモ）

- `[]` の中身は、 **型パラメータ**
- `<:` は、 **上限型境界**

https://docs.scala-lang.org/ja/tour/generic-classes.html

https://docs.scala-lang.org/ja/tour/upper-type-bounds.html

#### アドホック多相

Scala における 3 つ目の方法は、 trait への暗黙の変換か暗黙のパラメータ (implicit parameter) を使うこと。

```scala
trait CanPlus[A] {
  def plus(a1: A, a2: A): A
}

def plus[A: CanPlus](a1: A, a2: A): A = implicitly[CanPlus[A]].plus(a1, a2)
```

これは、以下の意味でまさにアドホックだ。

- 異なる `A` の型に対して、別の関数定義を提供できる
- (Int のような)型に対してソースコードへのアクセスがなくても、関数定義を提供できる
- 異なるスコープで、関数定義を有効化したり無効化したりできる

上記の書き方については、下記記事が参考になる。

http://eed3si9n.com/ja/scala-implicits-type-classes/

特に、context bound 構文については、下記記事を読むと理解が進む。

http://blog.takeda-soft.jp/blog/show/396.html

### sum 関数

アドホック多相の具体例として、`Int` のリストを合計する簡単な関数 `sum` を徐々に一般化していく。

```scala
def sum(xs: List[Int]): Int = xs.foldLeft(0) { _ + _ }
// sum: (xs: List[Int])Int
sum(List(1,2,3,4))
// res1: Int = 10
```

(foldLeft については、下記記事を読むと理解が深まる)

https://dev.classmethod.jp/articles/scala-foldright-foldleft/

#### Monoid

これを少し一般化してみる。
まず、`IntMonoid` という型を定義する。
`IntMonoid` は、

- 同じ型の値を生成する `mappend` 関数
- 0 を生成する `mzero` 関数

を含む。

```scala
object IntMonoid {
  def mappend(a: Int, b: Int): Int = a + b
  def mzero:                   Int = 0
}
// defined object IntMonoid
```

`IntMonoid` を `sum` 関数に代入することで、少し一般化された。

```scala
def sum(xs: List[Int]): Int =
  xs.foldLeft(IntMonoid.mzero)(IntMonoid.mappend)
// sum: (xs: List[Int])Int
sum(List(1, 2, 3, 4))
// res2: Int = 10
```

次に、あらゆる型について `Monoid` が定義できるように、 `IntMonoid` を抽象化し、 `IntMonoid` を `Int` の `Monoid` にする。

```scala
trait Monoid[A] {
  def mappend(a1: A, a2: A): A
  def mzero: A
}
// defined trait Monoid
// warning: previously defined object Monoid is not a companion to trait Monoid.
// Companions must be defined together; you may wish to use :paste mode for this.
object IntMonoid extends Monoid[Int] {
  def mappend(a: Int, b: Int): Int = a + b
  def mzero: Int = 0
}
// defined object IntMonoid
```

これにより、 `sum` に `List[Int]` と `Monoid[Int]` を渡して計算できる。

```scala
def sum(xs: List[Int], m: Monoid[Int]): Int =
  xs.foldLeft(m.mzero)(m.mappend)
// sum: (xs: List[Int], m: Monoid[Int])Int
sum(List(1, 2, 3, 4), IntMonoid)
// res4: Int = 10
```

これで `Int` を使わなくなったので、 `Int` を一般の型 `A` に置き換えできる。

```scala
def sum[A](xs: List[A], m: Monoid[A]): A =
  xs.foldLeft(m.mzero)(m.mappend)
// sum: [A](xs: List[A], m: Monoid[A])A
sum(List(1, 2, 3, 4), IntMonoid)
// val res1: Int = 10
```

最後の変更点は、 `Monoid` を implicit にすることで、明示的に渡さなくてもよくする。

```scala
def sum[A](xs: List[A])(implicit m: Monoid[A]): A =
  xs.foldLeft(m.mzero)(m.mappend)
// sum: [A](xs: List[A])(implicit m: Monoid[A])A
implicit val intMonoid = IntMonoid
// intMonoid: IntMonoid.type = IntMonoid$@efdc1cd
sum(List(1, 2, 3, 4))
// res6: Int = 10
```

また、この形の暗黙のパラメータは、 context bound で書かれることが多い。

```scala
def sum[A: Monoid](xs: List[A]): A = {
  val m = implicitly[Monoid[A]]
  xs.foldLeft(m.mzero)(m.mappend)
}
// sum: [A](xs: List[A])(implicit evidence$1: Monoid[A])A
implicit val intMonoid = IntMonoid
// intMonoid: IntMonoid.type = IntMonoid$@efdc1cd
sum(List(1, 2, 3, 4))
// res7: Int = 10
```

これで、どんな型の `List` でも合計できるようになり、 `sum` 関数はかなり一般化された。
テストのために、 `String` の `Monoid` である `StringMonoid` を書くこともできる。
`StringMonoid` などは、 `Monoid` という名前のオブジェクトに包むことにする。
その理由は、 Scala の implicit 解決ルールを利用するためだ。
具体的には、ある型の暗黙のパラメータを探す時、 Scala はスコープ内を探すが、それには探している型のコンパニオンオブジェクトも含まれるためだ。

```scala
trait Monoid[A] {
  def mappend(a1: A, a2: A): A
  def mzero: A
}

object Monoid {
  implicit val IntMonoid: Monoid[Int] = new Monoid[Int] {
    def mappend(a: Int, b: Int): Int = a + b
    def mzero: Int = 0
  }
  implicit val StringMonoid: Monoid[String] = new Monoid[String] {
    def mappend(a: String, b: String): String = a + b
    def mzero: String = ""
  }
}

def sum[A: Monoid](xs: List[A]): A = {
  val m = implicitly[Monoid[A]]
  xs.foldLeft(m.mzero)(m.mappend)
}

sum(List("a", "b", "c"))
// defined trait Monoid
// defined object Monoid
// sum: [A](xs: List[A])(implicit evidence$1: Monoid[A])A
// res0: String = abc
```

`sum` 関数に直接、異なるモノイドを渡すこともできる。
例えば、 `Int` の積算のモノイドのインスタンスを提供してみる。

```scala
val multiMonoid: Monoid[Int] = new Monoid[Int] {
  def mappend(a: Int, b: Int): Int = a * b
  def mzero: Int = 1
}
sum(List(1, 2, 3, 4))(multiMonoid)
// multiMonoid: Monoid[Int] = $anon$1@239bf02
// res1: Int = 24
```

### FoldLeft

`List` についても、関数を一般化してみる。
そのため、`foldLeft` 演算を一般化する。

```scala
trait Monoid[A] {
  def mappend(a1: A, a2: A): A
  def mzero: A
}

object Monoid {
  implicit val IntMonoid: Monoid[Int] = new Monoid[Int] {
    def mappend(a: Int, b: Int): Int = a + b
    def mzero: Int = 0
  }
  implicit val StringMonoid: Monoid[String] = new Monoid[String] {
    def mappend(a: String, b: String): String = a + b
    def mzero: String = ""
  }
}
val multiMonoid: Monoid[Int] = new Monoid[Int] {
  def mappend(a: Int, b: Int): Int = a * b
  def mzero: Int = 1
}

object FoldLeftList {
  def foldLeft[A, B](xs: List[A], b: B, f: (B, A) => B) = xs.foldLeft(b)(f)
}

def sum[A: Monoid](xs: List[A]): A = {
  val m = implicitly[Monoid[A]]
  FoldLeftList.foldLeft(xs, m.mzero, m.mappend)
}

sum(List(1, 2, 3, 4))
sum(List("a", "b", "c"))
sum(List(1, 2, 3, 4))(multiMonoid)
// defined trait Monoid
// defined object Monoid
// multiMonoid: Monoid[Int] = $anon$3@3abcfa12
// defined object FoldLeftList
// sum: [A](xs: List[A])(implicit evidence$1: Monoid[A])A
// res4: Int = 10
// res5: String = abc
// res3: Int = 24
```

これで、先ほどと同じ抽象化を行って、 `FoldLeft` 型クラスを抜き出す。

```scala
trait FoldLeft[F[_]] {
  def foldLeft[A, B](xs: F[A], b: B, f: (B, A) => B): B
}
object FoldLeft {
  implicit val FoldLeftList: FoldLeft[List] = new FoldLeft[List] {
    def foldLeft[A, B](xs: List[A], b: B, f: (B, A) => B) = xs.foldLeft(b)(f)
  }
}

def sum[M[_]: FoldLeft, A: Monoid](xs: M[A]): A = {
  val m = implicitly[Monoid[A]]
  val fl = implicitly[FoldLeft[M]]
  fl.foldLeft(xs, m.mzero, m.mappend)
}
// warning: two feature warnings; for details, enable `:setting -feature' or `:replay -feature'
// defined trait FoldLeft
// defined object FoldLeft
// sum: [M[_], A](xs: M[A])(implicit evidence$1: FoldLeft[M], implicit evidence$2: Monoid[A])A

sum(List(1,2,3,4))
// res6: Int = 10

sum(List("a", "b", "c"))
// res7: String = abc
```

以上から、 `Int` と `List` の両方が `sum` から抜き出された。

#### Cats の型クラス

上記の例における trait の `Monoid` と `FoldLeft` は　Haskell の型クラスに相当する。
Cats は多くの型クラスを提供する。

これらの型クラスは、必要な関数だけを含んだ部品に分けられている。

### メソッド注入 (enrich my library)

`Monoid` を使ってある型の 2 つの値を足す関数を書いた場合、このようになる。

```scala
def plus[A: Monoid](a: A, b: A): A = implicitly[Monoid[A]].mappend(a, b)
// plus: [A](a: A, b: A)(implicit evidence$1: Monoid[A])A

plus(3,4)
// res8: Int = 7
```

これに、演算子を提供したい。
ただし、1 つの型だけを拡張するのではなく、 `Monoid` のインスタンスを持つすべての型を拡張したい。
Simulacrum を用いて、 Cats スタイルでこれを行ってみる。

```scala
trait Monoid[A] {
  def mappend(a: A, b: A): A
  def mzero: A
}
object Monoid {
  object syntax extends MonoidSyntax

  implicit val IntMonoid: Monoid[Int] = new Monoid[Int] {
    def mappend(a: Int, b: Int): Int = a + b
    def mzero: Int = 0
  }
  implicit val StringMonoid: Monoid[String] = new Monoid[String] {
    def mappend(a: String, b: String): String = a + b
    def mzero: String = ""
  }
}
trait MonoidSyntax {
  implicit final def syntaxMonoid[A: Monoid](a: A): MonoidOps[A] = new MonoidOps[A](a)
}
final class MonoidOps[A: Monoid](lhs: A) {
  def |+|(rhs: A): A = implicitly[Monoid[A]].mappend(lhs, rhs)
}
// warning: one feature warning; for details, enable `:setting -feature' or `:replay -feature'
// defined trait Monoid
// defined object Monoid
// defined trait MonoidSyntax
// defined class MonoidOps

import Monoid.syntax._
// import Monoid.syntax._
3 |+| 4
// res9: Int = 7

"a" |+| "b"
// res10: String = ab
```

1 つの定義から、 `Int` と `String` の両方に `|+|` 演算子を注入することができた。

#### 標準データ型に対する演算子構文

このテクニックを使って、 Cats はごくたまに `Option` のような標準ライブラリデータ型へのメソッド注入も提供する。

```scala:Main.scala
import cats._, cats.syntax.all._

object Main extends App {
  println(1.some)
  println(1.some.orEmpty)
}
// Some(1)
// 1
```

しかし、 Cats の演算子の大半は型クラスに関連づけられている。
以上で、 Cats の雰囲気を掴めただろうか。