Scalaでshapelessを使ったジェネリックプログラミング

[shapeless](https://github.com/milessabin/shapeless)というScalaでジェネリックプログラミングできるライブラリを試してみたので紹介してみる。

## ジェネリックプログラミングとは

> ジェネリック（総称あるいは汎用）プログラミング (generic programming)はデータ形式に依存しないコンピュータプログラミング方式である。
> https://ja.wikipedia.org/wiki/ジェネリックプログラミング

データ形式に依存しないらしい。

## Scrap your boilerplate

Scrap your boilerplate (通称syb) は、Haskellでジェネリックプログラミングをするためのライブラリの1つで、shapelessはこれを実装しているとのこと。

とにかく例をみてみよう。

```scala:CaseClassの中身を+1する。
import shapeless._

object plus1 extends Poly1 {
  implicit val caseString = at[String](_ + "1")
  implicit val caseInt = at[Int](_ + 1)
}
val plus1All = everywhere(plus1)

case class User(id: Int, name: String)

plus1All(User(1, "bob"))
// User(2,bob1)
```

```scala:ネストしてても
plus1All(Some(1))
// Some(2)

plus1All((List(1, 2), Right("a"), Try(1)))
// (List(2, 3), Right("a1"), Success(2))

case class Manager(id: Option[Int], name: Either[String, User])
plus1All(Manager(Some(1), Right(User(1, "bob"))))
// Manager(Some(2),Right(User(2,bob1)))
```

ひたすらに中身を舐め回せる( 'ㅂ')

もう一つ、everythingを試してみよう。これは畳み込み関数だ。

```scala:Intのみ足し合わせる
object plus extends Poly2 {
  implicit val intCase = at[Int, Int](_ + _)
}
object size extends Poly1 {
  implicit val intCase = at[Int](x => x) // Intならその値を使い、
  implicit def default[T] = at[T](_ => 0) // それ以外は0。
}
def sum[T](t: T)(implicit everything: Everything[size.type, plus.type, T]) = everything(t)

sum((1, 2))
// 3

sum((1, (2, "3", Some(4))))
// 7

case class Foo(a: Int, b: String)
sum(Foo(1, "2") :: Foo(10, "20") :: Nil)
// 11
```

なんていうか...すごい！

まずplus1の定義で使ってるPolyから見ていこう。


## Poly

Poly1はFunction1のPolymorphic版のようなものだ。
複数型を処理できる関数になる。

```scala
object plus1 extends Poly1 {
  implicit val caseString = at[String](_ + "1")
  implicit val caseInt = at[Int](x => Some(x + 1))
}

plus1("a")
// a1

plus1(1)
// Some(2)
```

関数合成もできる。

```scala:plus1してshowするPolyを合成
object show extends Poly1 {
  def f(x: String) = s"result is $x"
  implicit val caseInt = at[Int](x => f(x.toString))
  implicit val caseString = at[String](f)
}

(show compose plus1)(1)
// "result is 2"

(plus1 andThen show)("1")
// "result is 11"
```

Polyはとてもおもしろい機能だ。ちなみにPolyは22まである。

ではHListとCoproductなどを見ていこう。これのおかげでcase class等の中身を処理できる。


## HList

HListは、ListとTupleを合わせたようなもの。
そして直積だ。(直積は、AとBとCと...のようなAND。例えばcase classやTuple)

```scala:単純に定義
import shapeless._

val xs = 1 :: "a" :: HNil
// xs: Int :: String :: HNil = ::(1, ::("a", HNil))
```

普通のリストと同じようなメソッドを持っている。

```scala:結合
val ys = 1d +: xs
// ys: Double :: Int :: String :: HNil = ::(1.0, ::(1, ::("a", HNil)))

val zs = xs ++ xs
// zs: Int :: String :: Int :: String :: HNil = ::(1, ::("a", ::(1, ::("a", HNil))))
```

```scala:取得
xs.head
// Int = 1
xs.tail
// String :: HNil = ::("a", HNil)
xs.take(2)
// Int :: String :: HNil = ::(1, ::("a", HNil))
xs(0)
// Int = 1
```

```scala:map
object toBoolean extends Poly1 {
  implicit val caseString = at[String](_ == "true")
  implicit val caseInt = at[Int](_ == 1)
}
xs.map(toBoolean)
// ::(true, ::(false, HNil))
```

```scala:foldLeft
object sum extends Poly2 {
  implicit val caseInt = at[Int, Int](_ + _)
  implicit val caseString = at[Int, String](_ + _.toInt)
}
(1 :: "2" :: 3 :: HNil).foldLeft(0)(sum)
// 6
```

他にもflatMapやらfilter等いつもの関数は用意されている。


## Coproduct

Eitherのようなもの。
そして直和だ。(直和は、AかBかCか...のようなOR。例えばEither)

[本家のWikiの例](https://github.com/milessabin/shapeless/wiki/Feature-overview:-shapeless-2.0.0#coproducts-and-discriminated-unions)がそのままで分かりやすかったので貼る...。

```scala
scala> type ISB = Int :+: String :+: Boolean :+: CNil
defined type alias ISB

scala> val isb = Coproduct[ISB]("foo")
isb: ISB = foo

scala> isb.select[Int]
res0: Option[Int] = None

scala> isb.select[String]
res1: Option[String] = Some(foo)

object size extends Poly1 {
  implicit def caseInt = at[Int](i => (i, i))
  implicit def caseString = at[String](s => (s, s.length))
  implicit def caseBoolean = at[Boolean](b => (b, 1))
}

scala> isb map size
res2: (Int, Int) :+: (String, Int) :+: (Boolean, Int) :+: CNil = (foo,3)

scala> res2.select[(String, Int)]
res3: Option[(String, Int)] = Some((foo,3))
```


## Generic

具体的な型を、Genericを使い`HList`や`Coproduct`に**相互変換**できる。
最初に紹介したcase class等の中身を処理できたのは、これのおかげだ。

例えば以下のような代数的データ型を定義する。

```scala
sealed trait Animal
case class Dog(name: String, age: Int) extends Animal
case class Cat(name: String, state: Int) extends Animal
```

GenericでDogを、HListに変換してまた戻す。

```scala
val hachi = Dog("hachi", 2)
val gHachi = Generic[Dog].to(hachi) // hachi :: 2 :: HNil
hachi == Generic[Dog].from(gHachi)
```

`String :: Int :: HNil` という型からDogに戻せたので、型さえあってれば別の具体的な型にできる。

```scala
Generic[Cat].from(gHachi) // Cat(hachi,2)
```

そしてAnimalのGenericは、DogかCatなのでCoproductで表せる。
同じように相互変換してみる。

```scala:Animalの相互変換
val animal = Generic[Animal].to(hachi)
// Generic.Aux[Animal, Dog :+: Cat :+: CNil] = Dog(hachi,2)

animal.select[Dog]
// Option[Dog] = Some(Dog(hachi,2))

animal.select[Cat]
// Option[Cat] = None

Generic[Animal].from(animal)
// Animal = Dog(hachi,2)
```

相互変換を利用してmapしてみる。

```scala
object plus1 extends Poly1 {
  implicit val caseString = at[String](_ + "1")
  implicit val caseInt = at[Int](_ + 1)
}

def f[A, L <: HList](a: A)(implicit gen: Generic.Aux[A, L], mapper: Mapper.Aux[plus1.type, L, L]) = 
  gen.from(gen.to(a) map plus1)

f(Dog("hachi", 1))
//  Dog("hachi1", 2)

f((1, "a"))
// (2, "a1")

f(Some(1))
// Some(2)
```

うーむすごい...( 'ㅂ')

[Monoidを使った例](https://github.com/milessabin/shapeless/blob/master/examples/src/main/scala/shapeless/examples/monoids.scala)とかあった。

```scala:caseclassそのままappendする例
Foo(13, "foo") |+| Foo(23, "bar")
// Foo(36, "foobar")
```

[scalazのMonoidを使えるもの](https://github.com/typelevel/shapeless-contrib#scalaz--shapeless--profit)も用意されているようだ。

他にも機能を紹介していく。

## Record

HListにキーがついたMapみたいなもの。

```scala
val mx = "a" ->> 1 :: "b" ->> 2 :: HNil

mx("a")
// 1

mx("b")
// 2

mx("c")
//<console>:39: error: No field String("c") in record shapeless.::[Int with shapeless.labelled.KeyTag[String("a"),Int],shapeless.::[Int with shapeless.labelled.KeyTag[String("b"),Int],shapeless.HNil]]
//       mx("c")

mx.keys
// a :: b :: HNil

mx.values
// 1 :: 2 :: HNil

mx + ("c" ->> 3)
// 1 :: 2 :: 3 :: HNil

mx.updated("a", 9)
// 9 :: 2 :: HNil
```

これ使えばcase classもキー付きで扱える。

例にある通り、存在しないキーで取得するとコンパイルエラーになる。
これは次の仕組みが使われている。


## Singleton-typed literals

シングルトンなリテラルの型を扱えるものだ。
例えば1だったらInt(1)という型で、Scalaでは普通ならInt(1)という型は書けない。
shapelessでは、マクロを使ってInt(1)型を扱えるようにしている。

試しにshapelessで1しか受け取れない関数を作ってみる。

```scala:1しか受け付けない関数
import syntax.singleton._

val one = 1.witness
def f(x: one.T) = x

f(1)
// 1

f(2)
/*
<console>:40: error: type mismatch;
 found   : Int(2)
 required: one.T
    (which expands to)  Int(1)
       f(2)
         ^
*/
```

単純な例であれば、簡単なマクロを書くだけでInt(1)は取得できる。

```scala
import scala.language.experimental.macros
import scala.reflect.macros.Context

trait Gen { type T }
def witness[T](t: T): Gen = macro witness_impl[T]
def witness_impl[T](c: Context)(t: c.Expr[T]) = {
  import c.universe._
  q"new Gen { type T = ${t.actualType} }"
}
```

実行すると

```scala
val one = witness(1)
// one: AnyRef with Gen{type T = Int(1)} = cmd5$$anon$1@6a1c6870

def f(x: one.T) = x
f(1)
// 1

f(2)
/*
Compilation Failed
Main.scala:45: type mismatch;
 found   : Int(2)
 required: cmd6.One
    (which expands to)  Int(1)
f(2)
  ^
*/
```

このようにマクロを経由することで普通は扱えない型を使えるようにしている...( 'ㅂ')


## おわり

元はSprayでHListが使われていたので調べてみたんだが、なんとも面白いライブラリだ...。

他にも紹介してない機能は色々あるので、興味あれば[本家のwiki](https://github.com/milessabin/shapeless/wiki/Feature-overview:-shapeless-2.0.0)や[サンプルコード](https://github.com/milessabin/shapeless/tree/dee18017c0c0284cde270948a16382ffc1f41273/examples/src/main/scala/shapeless/examples)を見るといいと思う。

shapelessでデータを舐め回しましょう。(^ω^)ﾍﾟﾛﾍﾟﾛ

(今回使用したshapelessのバージョンは2.2.5)