LoginSignup
6
5

More than 5 years have passed since last update.

shapeless - Lens

Posted at

shapelessのLensを試してみる。

試す

簡単な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> 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> 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> 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> 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> (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> 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> 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が使われる。

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の他にもprismopticでも同じもののようだ。

仕組み

例えばlens[Foo].aであれば、Fooをキー付きのHList(Recordと呼ばれてる)に変換して、そこから指定したキーaで値を取得・更新するLensを作っている。
本体のコードはこちら

おわり

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

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

6
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
5