Upload
naoki-aoyama
View
2.366
Download
0
Embed Size (px)
Citation preview
今から始めるLens/Prism
2016/10/08 Scala関西 Summit 2016
お前誰だよ?
Naoki Aoyamaアドテク系 Scala エンジニア
コミッターTwitter: , GitHub: Monocle
@AoiroAoino @aoiroaoino
Lens/Prism ってご存知ですか?
Scala で Lens/Prism を使うモチベーションネストした構造に対する copy メソッドのネスト地獄様々なデータを共通のインターフェースで操作できる非侵入的に外側から追加できる
特定のアーキテクチャに依存しない概念汎用的に使える
などなど
Lens って何?getter/setter を抽象的にしたものpurely functional reference
実は
余状態コモナド余代数
(Costate Comonad Coalgebra)
getter/setter ってなんだっけ?(広く一般的な意味で)オブジェクト指向言語において、ある field の値を取得するメソッドが getter で、 fieldに値をセットするのが setter。(広く一般的な意味で)クラスのメソッドとして定義され、getFoo、setFoo と 名
付けられる事が多い、おなじみの例のアレ。
getter/setter 再考特定の class に依存しない、汎用的な関数と捉えてみる
getter の仕事とはオブジェクトからある値を取り出す
素直にシグネチャとして表してみると…オブジェクト S からある値 A を取り出すS => A
// getterdef get[S, A]: S => A
setter の仕事とはオブジェクトの一部をある値に変更する
素直にシグネチャとして表してみると…オブジェクト S の一部をある値 A に変更するS => A => S
// setterdef set[S, A]: S => A => S
あるオブジェクト S の各 field に対して前述のようなgetter/setter が存在した時、 それらを使って汎用的な
get/set メソッドの提供を考える。↓↓↓
その仕組みを実現するものを Lens と呼ぶ。
get/set Lensclass Lens[S, A]( getter: S => A, setter: S => A => S) { def get(s: S): A = getter(s)
def set(s: S, a: A): S = setter(s)(a)
def modify(s: S, f: A => A): S = set(s, f(get(s)))}
get/set Lensgetter/setter をコンストラクタの引数として与える。
class Lens[S, A]( getter: S => A, setter: S => A => S) {
Lens の値を定義するcase class User(id: Int, name: String)
// User#id �対�� Lensval _id = new Lens[User, Int]( _.id, // getter user => newId => user.copy(id = newId) // setter)
// User#name �対�� Lensval _name = new Lens[User, String]( _.name, // getter user => newName => user.copy(name = newName) // setter)
使い方val user1 = User(100, "John Doe")
_id.get(user1)// res: Int = 100
_name.get(user1)// res: String = John Doe
_name.set(user1, "Naoki Aoyama")// res: User = User(100,Naoki Aoyama)
_name.modify(user1, _.toUpperCase)// res: User = User(100,JOHN DOE)
Lens LawsLens には満たすべき法則がある。
get/setset(s, get(s)) == s
set/getget(set(s, a)) == a
set/setset(set(s, a1), a2) == set(s, a2)
ネストしたデータが対象の時は?ネストした構造に対する copy メソッドのネスト地獄を
どう解決するか。
Lens の合成を考えるclass Lens[S, A](getter: S => A, setter: S => A => S) {
def get(s: S): A = getter(s) def set(s: S, a: A): S = setter(s)(a) def modify(s: S, f: A => A): S = set(s, f(get(s)))
def ̂|->[B](other: Lens[A, B]): Lens[S, B] = new Lens( s => other.get(this.get(s)), // getter s => b => this.set(s, other.set(this.get(s), b)) //setter )}
Lens の合成を考えるdef ̂|->[B](other: Lens[A, B]): Lens[S, B] = new Lens( s => other.get(this.get(s)), // getter s => b => this.set(s, other.set(this.get(s), b)) //setter)
試してみようcase class Message(user: User, body: String)
// Message#id �対�� Lensval _body = ...
// Message#user �対�� Lensval _user: Lens[Message, User] = new Lens( _.user, message => newUser => message.copy(user = newUser))
Beforeval message = Message(User(100, "John Doe"), "Hello")
message.user // res: User = User(100,John Doe)message.user.name // res: String = John Doe
message.copy( user = user.copy( name = "aoino" )) // res: Message = Message(User(100,aoino), Hello)
message.copy( user = user.copy( name = message.user.name.toUpperCase )) // res: Message = Message(User(100,JOHN DOE),Hello)
A�erval message = Message(User(100, "John Doe"), "Hello")
_user.get(message)// res: User = User(100,John Doe)
(_user ̂|-> _name).get(message)// res: String = John Doe
(_user ̂|-> _name).set(message, "aoino")// res: Message = Message(User(100,aoino),Hello)
(_user ̂|-> _name).modify(message, _.toUpperCase)// res: Message = Message(User(100,JOHN DOE),Hello)
ところで
オブジェクト S に Option 型の field が存在する場合にget メソッドが適切に定義できるだろうか?
A == Option[B] となるような Lens を考えてみる。
class Lens[S, A]( getter: S => A, setter: S => A => S) { // ...}
すると、A が Option[B] となるような場合にget メソッドが実装出来ない。
class BadLens[S, B]( getter: S => B, setter: S => B => S) { def get(s: S): B = getter(s) match { case Some(b) => b case None => ??? /// 何�返����� }
/// ...}
ここで、Prism を導入する。
getter の取りうる引数に対してsetter が返すことが可能な値が足りない場合に
Lens だけではカバーしきれない。↓↓↓
その仕組みを実現するものを Prism と呼ぶ。
Prismclass Prism[S, A]( _getOption: S => Option[A], _reverseGet: A => S) { def getOption(s: S): Option[A] = _getOption(s)
def reverseGet(a: A): S = _reverseGet(a)}
まずは、Some, None に対する Prism を定義する。
// Some �対�� Prismdef _some[A] = new Prism[Option[A], A]( { case Some(a) => Some(a); case None => None }, Some.apply)
// None �対�� Prismdef _none[A] = new Prism[Option[A], Unit]( { case None => Some(()); case Some(_) => None }, _ => None)
一部のユーザーに Email アドレスの情報を持たせたくなった場合を想定する。
case class User(id: Int, name: String, email: Option[String])
// User#email �対�� Prismval _email = new Lens[User, Option[String]]( _.email, user => newEmail => user.copy(email = newEmail))
_some.getOption(_email.get(User(1, "aaa", Option("email"))))// res: Option[String] = Some(email)
_some.getOption(_email.get(User(1, "aaa", None)))// res: Option[String] = None
とてもダサい!!
_email.get, _some.getOption を直接呼び出しているコードの見通しが悪い
getOption の内部がより複雑だった場合
Lens の時と同じように合成を考えれば良い。
Q: Lens と Prism を合成すると何になる?
LensPrismその他
A: Optional
Optics Hierarchie (Monocle)
ref. Monocle の よりREADME.md
Optics Hierarchie (lens)
ref. lens の よりREADME.md
Optional は Lens と Prism の両方の性質を持つ Optics※ 下記コードは Monocle の よりOptional.scala
object Optional {
def apply[S, A](_getOption: S => Option[A])(_set: A => S => S): Optional new Optional[S, A]{ // ... }}
Monocle を使って書くと以下の通り。
val user = User(1, "aaa", Some("email"))
(_email composePrism _some).getOption(user)// res: String = email
様々な合成メソッドのエイリアスが記号として提供されている。
(_email ̂<-? _some).getOption(user)// res: String = email
\( 'ω')/ウオオオオオアアアーーーッ!
^|->>
^|-?
^<-?
^|->
^<->
\(՞ةڼ◔)/ウオオオオオアアアーーーッ!?!?!?
ここまでのまとめ
Lens/Prism の概念と使い方を理解したOptics Hierarchie を知った
Lens の種類
Lens にはコンセプトや実装に応じていくつか種類がある
get/set lensget/modify lensIso lensStore Comonad Lensvan Laarhoven lensand so on ...
van Laarhoven Lens
van Laarhoven Lens とは?2009 年 7 月に Twan van Laarhoven さんが書いた のアイディアが元になった Lens で、Haskell の や Scala
の のコンセプトの基礎になってる。
bloglens
Monocle
def Lens[S, A, F[_]: Functor] = S => (A => F[A]) => F[S]
type Lens s a = forall f. Functor f => (a -> f a) -> s -> f s
閑話休題
traverse 関数はご存知ですか?
def traverse[G[_]: Applicative, A, B](fa: F[A])(f: A => G[B]): G[F[B]]
例えば、F を List、G を Option とすると、traverse 関数はリスト内の値に f を適用し、全て Some だった場合には
f の適用結果を Some に包んで返し一つでも None の場合は None を返します。
def isEven(v: Int): Option[Int] = if (v % 2 == 0) Some(v) else None
List(2, 4, 5).traverse(isEven)// res: Option[List[Int]] = None
List(2, 4, 6).traverse(isEven)// res: Option[List[Int]] = Some(List(2, 4, 6))
※ 上記コードは Scalaz を使用しています。
閑話休題おわり
理解する為に getter/setter を再考してみよう。
setter の再考
setter の再考冒頭に出てきた setter の型は以下の通り。
def setter: S => A => S
これは modify メソッドの特殊形と考えられる。なので、modify の signature を取り入れて
def setter: S => (A => A) => S
と、改めて定義する。
この型に見覚えない?
trait Functor[F[_]] { // F[A] => (A => B) => F[B] def map[A, B](fa: F[A])(f: A => B): F[B]}
つまり、我々は set/modify をするのに各 field に対するFunctor#map が欲しいのだ。
しかし、Functor の instance を class の field 毎にそれぞれ定義することは現実的ではない...
Functor#map は Traverse#traverse で定義できるtrait Traverse[F[_]] extends Functor[F] {
// F[A] => (A => G[B]) => G[F[B]] def traverse[G[_]: Applicative, A, B](fa: F[A])(f: A => G[B]): G[F[B
type Id[A] = A
// Id � Applicative � instance �����自明 implicit def idApplicative[A] = new Applicative[Id] { ... }
// F[A] => (A => Id[B]) => Id[F[B]] def map[A, B](fa: F[A])(f: A => B): F[B] = traverse[Id, A, B](fa)(f)}
つまり、この traverse の部分を同等な関数に置き換えることで、Functor#map のような関数を得られる。このF[A], F[B] を S と置いて Setter という名前をつけよう。
type Setter[S, A] = S => (A => Id[A]) => Id[S]
この Setter[S, A] を用いて set/modify メソッドが作れる。
// Setter[S, A] => S => (A => A) => Sdef modify[S, A](setter: Setter[S, A])(s: S)(f: A => A): S = setter(s)(a => (f(a): Id[A]))
// Setter[S, A] => S => A => Sdef set[S, A](setter: Setter[S, A])(s: S)(a: A): S = setter(s)(_ => (a: Id[A]))
動作確認val _id: Setter[User, Int] = (user: User) => (f: Int => Id[Int]) => user.copy(id = f(u.id))
scala> modify(_name)(User(100, "John Doe"))(_.toUpperCase)// res: User = User(100,JOHN DOE)
scala> set(_name)(User(100, "John Doe"))("aoiroaoino")// res: User = User(100,aoiroaoino)
\( 'ω')/ウオオオオオアアアーーーッ!
getter の再考
getter の再考冒頭に出てきた getter の型は以下の通り。
def getter: S => A
取得するだけでなく、関数を適用した結果を返すように
def getter: S => (A => A) => A
と、改めて定義する。
この型に見覚えない?
trait Foldable[F[_]] { // F[A] => (A => B) => B def foldMap[A, B](fa: F[A])(f: A => B)(implicit mb: Monoid[B]): B}
つまり、我々は get をするのに各 field に対するFoldable#foldMap が欲しいのだ。
しかし、Foldable の instance を class の field 毎にそれぞれ定義することは現実的ではない...
Foldable#foldMap は Traverse#traverse で定義できるtrait Traverse[F[_]] extends Functor[F] {
// F[A] => (A => G[B]) => G[F[B]] def traverse[G[_]: Applicative, A, B](fa: F[A])(f: A => G[B]): G[F[B
// Const � Applicative � instance �����自明 case class Const[A, B](runConst: A) implicit def monoidApplicative[A](A: Monoid[A]) = new Applicative[({type λ[X] = Const[A, X]})#λ] { ... }
// F[A] => (A => B) => B def foldMap[A, B](fa: F[A])(f: A => B)(implicit mb: Monoid[B]): B = traverse[({type λ[X] = Const[B, X]})#λ, A, Nothing](fa)(f)}
Setter と同様に traverse の部分を同等な関数に置き換えることで、Foldable#foldMap のような関数を得られる。この F[A] を S と置いて Getter という名前をつけよう。
type Getting[R, S, A] = S => (A => Const[R, A]) => Const[R, S]
// S => (A => Const[A, A]) => Const[A, S]type Getter[S, A] = Getting[A, S, A]
この Getter[S, A] を用いて get メソッドが作れる。
// Getter[S, A] => S => Adef get[S, A](getter: Getter[S, A])(s: S): A = getter(s)(a => Const(a)).runConst
さて、我々は似たような signature を持つ Getter/Setter を手に入れた。これらを並べて見てみよう。
type Setter[S, A] = S => (A => Id[A]) => Id[S]
type Getter[S, A] = S => (A => Const[A, A]) => Const[A, S]
Const と Id の部分を Functor の instance を要求する型変数に置き換えられそう。
これで van Laarhoven lens の定義を理解できましたね?
def Lens[S, A, F[_]: Functor] = S => (A => F[A]) => F[S]
type Lens s a = forall f. Functor f => (a -> f a) -> s -> f s
しかし、ここで一つ疑問が湧きませんか?
Q: van Laarhoven lens はうまく合成できるのだろうか?
Getter も Setter も中身は traverse と同等の関数ですね。なので、代表して traverse の合成を見てみましょう。
f に Id/Const が入ります。 構造のネストする順番と合成の順序が一致し、左から右へと辿れますね。
traverse :: (Applicative f, Traversable t) => (a -> f b) -> t a -> f (t b)
traverse . traverse :: (Applicative f, Traversable t, Traversable t1) => (a -> f b) -> t (t1 a) -> f (t (t1 b))
traverse . traverse . traverse :: (Applicative f, Traversable t, Traversable t1, Traversable t2) => (a -> f b) -> t (t1 (t2 a)) -> f (t (t1 (t2 b)))
おやおや?この Haskell の関数合成に使う (.) 演算子が、Scala や Java などの (.) 演算子に見えませんか?
Haskell で van Laarhoven Lens の合成はこう書ける。
ghci> set (_2 . _1) 42 ("hello",("world","!!!"))("hello",(42,"!!!"))
A: Haskell だと超美しく合成できる...
まとめLens/Prism の概念と使い方を完全マスターしたOptics Hierarchie と呼ばれる階層が存在するLens には種類があるvan Laarhoven lens はやばい