shapeless - Lens

shapelessのLensを試してみる。

## 試す

```scala:簡単なLensの例
scala> import shapeless._
//import shapeless._

scala> case class Foo(a: Int, b: Int)
//defined class Foo

scala> val aLens = lens[Foo].a
//aLens: shapeless.Lens[Foo,Int] = shapeless.Lens$$anon$7@57039e80

scala> aLens.get(Foo(1,2))
//res1: Int = 1

scala> aLens.set(Foo(1,2))(3)
//res2: Foo = Foo(3,2)
```

FooのaにアクセスするLensを作って、Fooのインスタンスを渡すとaが取れる。セットもできる。

もちろんネストしてても。

```scala
scala> case class Bar(foo: Foo)
//defined class Bar

scala> val aLens = lens[Bar].foo.a
//aLens: shapeless.Lens[Bar,Int] = shapeless.Lens$$anon$7@389f9397

scala> aLens.get(Bar(Foo(1,2)))
//res3: Int = 1

scala> aLens.set(Bar(Foo(1,2)))(3)
//res4: Bar = Bar(Foo(3,2))
```

存在しないキーを指定してLensを作ろうとするとコンパイルエラー。

```scala
scala> lens[Bar].fo
//<console>:18: error: could not find implicit value for parameter mkLens: shapeless.MkSelectDynamicOptic[shapeless.Lens[Bar,Bar],Bar,shapeless.tag.@@[Symbol,String("fo")],B]
//       lens[Bar].fo
```

## その他の細かな機能

複数繋げてタプルで取得やセットができる。

```scala
scala> val aLens = lens[Bar].foo.a
//aLens: shapeless.Lens[Bar,Int] = shapeless.Lens$$anon$7@6da49f8e

scala> val bLens = lens[Bar].foo.b
//bLens: shapeless.Lens[Bar,Int] = shapeless.Lens$$anon$7@3f8bb4da

scala> val abLens = aLens ~ bLens
//abLens: shapeless.ProductLensBuilder[Bar,(Int, Int)] = shapeless.Lens$$anon$1@32d63616

scala> abLens.get(Bar(Foo(1,2)))
//res19: (Int, Int) = (1,2)

scala> abLens.set(Bar(Foo(1,2)))((3,4))
//res20: Bar = Bar(Foo(3,4))
```

パスを定義することも。

```scala
scala> val aLens = lens[Bar](^.foo.a)
//aLens: shapeless.Lens[Bar,Int] = shapeless.Lens$$anon$7@6e72e7a2

scala> aLens.get(Bar(Foo(1,2)))
//res8: Int = 1
```

`>>`の記号でキーを指定したりインデックスで番号を指定することも。

```scala
scala> (lens[Bar] >> 'foo).get(Bar(Foo(1,2)))
//res26: Foo = Foo(1,2)

scala> (lens[Bar] >> 0).get(Bar(Foo(1,2)))
//res27: Foo = Foo(1,2)
```

パターンマッチも。

```scala
scala> val aLens = lens[Bar].foo.a
//aLens: shapeless.Lens[Bar,Int] = shapeless.Lens$$anon$7@6da49f8e

scala> val aLens(a) = Bar(Foo(1,2))
//a: Int = 1
```

合成も。

```scala
scala> val aLens = lens[Foo].a compose lens[Bar].foo
//aLens: shapeless.Lens[Bar,Int] = shapeless.Lens$$anon$7@6e3b3bf5

scala> aLens.get(Bar(Foo(1,2)))
//res28: Int = 1
```

## Prism

Lensは取得するとそのまま値が返るが、PrismはOptionで返る。

sealed trait等(Coproduct)を指定した場合、取得できないかもしれないのでPrismが使われる。

```scala
sealed trait Tree[T]
case class Leaf[T](t: T) extends Tree[T]
case class Node[T](l: Tree[T], r: Tree[T]) extends Tree[T]

scala> val leafLens = lens[Tree[Int]][Leaf[Int]].t
//leafLens: shapeless.Prism[Tree[Int],Int] = shapeless.Lens$$anon$8@76a3f102

scala> leafLens.get(Leaf(1))
//res48: Option[Int] = Some(1)

scala> leafLens.get(Node(Leaf(1), Leaf(2)))
//res49: Option[Int] = None
```

`lens`の他にも`prism`や`optic`でも[同じもの](https://github.com/milessabin/shapeless/blob/master/core/src/main/scala/shapeless/package.scala#L63-L65)のようだ。


## 仕組み

例えば`lens[Foo].a`であれば、Fooをキー付きのHList(Recordと呼ばれてる)に変換して、そこから指定したキーaで値を取得・更新するLensを作っている。
[本体のコードはこちら](https://github.com/milessabin/shapeless/blob/master/core/src/main/scala/shapeless/lenses.scala)


## おわり

`~`で繋げてタプルでまとめて取得できるLensBuilderは便利だなと思いました(小並感)

SelectDynamicが使われてるので補完がきかなくてダルい...。Monocleは補完効くのでいいなぁ。