Shapelessはいいぞ

この記事は、ドワンゴ Advent Calendar 2019の12日目の記事です。

普段はscalaでバックエンドサーバーを書いているエンジニアです
今回は便利だけどあまり使い方が知られていなさそうなshapelessについて簡単な例を交えて紹介したいと思いま

# shapeless とはなんぞや
公式では「generic programming for Scala」と書かれていますが、要は型安全なメタプログラミングをするためのライブラリです[1]

shapelessは汎用的な処理を書くときに便利な道具なので他のライブラリがshaplessに依存していたりします
有名なものだとJSON libraryの[circe](https://github.com/circe/circe)やconfファイルの読み込むときに便利な[pureconfig](https://github.com/pureconfig/pureconfig)等があります


# まずはimplicit parameterのおさらい
shapelessを使うにはimplicitをうまく使ったライブラリなのでこの機能をおさらいします。

implicit parameterの機能は一言でいえば、指定した型をコンパイル時に探してコードを自動的に導出してくれる機能です[2]

例えばこんなFooという型が合った時に

```scala
case class Foo[A]()
object Foo {
  implicit def string: Foo[String] = new Foo[String]()
  implicit def int: Foo[Int] = new Foo[Int]()
  implicit def bool(implicit foo: Foo[Int]): Foo[Boolean] = new Foo[Boolean]()
}
```
こんな感じにコードをコンパイル時に埋めてくれます

```scala

def findFoo[A]()(implicit foo: F[A]): Unit = ()

findFoo[String]()
findFoo[Int]()
findFoo[Boolean]()

//上の行をコンパイルすると下のような感じのコードになる
findFoo[String]()(Foo.string) 
findFoo[Int]()(Foo.int)
findFoo[Boolean]()(Foo.bool(Foo.int))
```

# shapelessを使ってみよう
今回は2つのインスタンスを比較して差分表現を出力するDiffクラスを例に考えてみます

```scala
trait Diff[A] {
  def show(a: A, b: A): Seq[String]
}
object Diff {
  def show[A](a: A, b: A)(implicit diff: Diff[A]): Seq[String] = diff.show(a, b)
}
```
shapelessを使うと実際の型を気にせずDiffインスタンスを生成させることができます

まずはこの`Diff[Bar]`をshapelessのちからを使って生成できるようにしてみましょう

```scala
case class Bar(a: Int, b: Boolean, s: Symbol)
```

shapelessは任意のcase classやsealed traitで記述された型を`HList`や`Coproduct`という型に変換することができます

HListは異なる型を入れられるリストで

```scala
//一部抜粋
sealed trait HList
case class ::[+H, +T <: HList](head : H, tail : T) extends HList
sealed trait HNil extends HList
case object HNil extends HNil
```
中の構造はタプルに近く

```scala
val hlist: String :: Int :: Boolean :: HNil = "hoge" :: 123 :: true :: HNil
val tuple = ("hoge", (123, (true, ()))) //これに構造は近い
```

Coproductは任意の数の型をから選択できるEitherのような存在で

```scala
//一部抜粋
sealed trait Coproduct
sealed trait CNil extends Coproduct
case class Inl[+H, +T <: Coproduct](head : H) extends :+:[H, T]
case class Inr[+H, +T <: Coproduct](tail : T) extends :+:[H, T]
```
中の構造もEitherに近いです

```scala
val cop: String :+: Int :+: Boolean :+: CNil = Inr(Inl(123))
val either: Either[String, Either[Int, Either[Boolean, Nothing]]] = Left(Right(Right(true)) //これに構造は近い
```


さてshapelessのGenericインスタンスを使うcase classをHListと相互変換できるようになります
これを使ってBarを`Int :: Boolean :: Symbol :: HNil`に変換するとこうなります


```scala
import shapeless._
def bar2hlist[L <: HList](bar: Bar)(implicit gen: Generic.Aux[Bar, L]): L = gen.to(bar)
bar2hlist(Bar(123, true, 'bar)) // 123 :: true :: 'bar :: HNil
```

Genericを使って要素毎のDiffからDiffを導出させます

```scala

trait ProductGenericInstance {
  // Diff[Repr <: HList]からDiff[X]を作成
  implicit def deriveProduct[X, Repr <: HList](
    implicit
    gen: Generic.Aux[X, Repr],
    dr: Diff[Repr]
  ): Diff[X] = (a, b) => dr.diff(gen.to(a), gen.to(b))

  // Diff[H] と Diff[T <: Hlist]から Diff[H :: T] を作る
  implicit def product[H, T <: HList](implicit head: Diff[H], tail: Diff[T]): Diff[H :: T] = {
    //Listのhead tailと似た感じでハンドリングする
    case (ah :: at, bh :: bt) => head.diff(ah, bh) ++ tail.diff(at, bt)
  }

  implicit val hnil: Diff[HNil] = (_, _) => Nil
}

```
これだけでは`Diff[H]`にはまる`Diff[Int]`, `Diff[Boolean]`, `Diff[Symbol]`がわからないのでこれらはequalsをつかってDiffをつくります

```scala
trait AnyInstance {
  implicit def other[A](implicit ev1: A <:!< Coproduct, ev2: A <:!<  HList): Diff[A] = {
    case (a, b) if a != b =>
      s"$a => $b" :: Nil
    case _ => Nil
  }
}
```

これらを組み合わせるとBar型の自体の知識なしに`Diff.show`を使えるようになります

```scala
case class Bar(a: Int, b: Boolean, s: Symbol)

object Diff extends ProductGenericInstance { ... }
trait ProductGenericInstance extends AnyInstance { ... }
trait AnyInstance { ... }

Diff.show(Bar(1, true, 'a), Bar(1, true, 'a))  // Seq.empty

Diff.show(Bar(1, true, 'a), Bar(1, false, 'b)) // Seq("true => false", "'a" => "'b")
```

shaplessは便利でしょ？

# 最後に
今回はshapelessを使って各クラスの実装の詳細に立ち入らず、インスタンス間差分を計算するコードを記述できた

以下のことに触れなかったが、これらを実現することは比較的容易にできます
 - Coproductを利用してサブタイプに対応したDiffを作る方法
 - 差分が出たところのフィールド名や構造がネストしているときにフィールドのパスを差分の表現に含める方法
 - 再帰的な型のDiffを作る方法 (eg. ```case class Hoge(a: Int, b: Option[Hoge]) ```)
 - 独自ルールをDiffに差し込む方法（SeqやMapなどはequalsじゃない方法で比較したいですよね？）

shapelessに関する日本語のドキュメントはあまり多くはありませんが、良質な英語のドキュメントや動画が数多く存在します
またソースコード自体についているコメントもとてもわかりやすいのでぜひ使ってみましょう

# 参考文献
Generic周りのガイド https://books.underscore.io/shapeless-guide/shapeless-guide.html
機能概要(公式wiki) https://github.com/milessabin/shapeless/wiki/Feature-overview%3A-shapeless-2.0.0

----
[1] https://github.com/milessabin/shapeless
[2] http://dwango.github.io/scala_text_previews/trait-tut/implicit.html