van laarhoven lens
Post on 03-Mar-2017
299 Views
Preview:
TRANSCRIPT
Lens の基本と基礎2017/02/26 Scala Matsuri 2017 - day 2
お前誰だよ?
Naoki Aoyamaマーベリック株式会社
アドテク系 Scala エンジニアコミッター
Twitter: , GitHub: Monocle
@AoiroAoino @aoiroaoino
Lens ってご存知ですか?
Scala で Lens を使うモチベーションネストした構造に対する 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
// getter def get[S, A]: S => A
setter の仕事とはオブジェクトの一部をある値に変更する
素直にシグネチャとして表してみると…オブジェクト S の一部をある値 A に変更するS => A => S
// setter def 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 �対�� Lens val _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 compose[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 compose[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 �対�� Lens val _body = ...
// Message#user �対�� Lens val _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 compose _name).get(message) // res: String = John Doe
(_user compose _name).set(message, "aoino") // res: Message = Message(User(100,aoino),Hello)
(_user compose _name).modify(message, _.toUpperCase) // res: Message = Message(User(100,JOHN DOE),Hello)
ここまでのまとめ
Lens の基本的な概念を完全に理解した
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
型のイメージは以下の通り。
type Lens[F[_]: Functor, S, A] = S => (A => F[A]) => F[S]
実際に定義すると
abstract class Lens[F[_]: Functor, S, A] { def run(s: S)(f: 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 を使用しています。
閑話休題おわり
さて、まずは van Laarhoven lens を理解する為に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] = Lens[Id, S, A]
この Setter[S, A] を用いて set/modify メソッドが作れる。// Setter[S, A] => S => (A => A) => S def modify[S, A](setter: Setter[S, A])(s: S)(f: A => A): S = setter.run(s)(f)
// Setter[S, A] => S => A => S def set[S, A](setter: Setter[S, A])(s: S)(a: A): S = setter.run(s)(_ => a)
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 Getter[S, A] = Lens[({type F[X] = Const[A, X]})#F, S, A]
この Getter[S, A] を用いて get メソッドが作れる。// Getter[S, A] => S => A def get[S, A](getter: Getter[S, A])(s: S): A = getter.run(s)(a => Const(a)).getConst
試しに User#name に対する Lens を定義してみる。def _name[F[_]: Functor] = new Lens[F, User, String] { def run(user: User)(f: String => F[String]): F[User] = Functor[F].map(f(user.name))(n => user.copy(name = n)) }
本当に動くか試してみよう!!
さて、我々は似たような 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 の定義を理解できましたね?type Lens[F[_]: Functor, S, A] = 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 の合成を見てみましょう。
しかし、Scala での traverse の(関数合成の意味での)compose は辛いので、 Haskell で試します...
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: 合成もできる!
まとめ
Lens の基本を完全に理解したLens には種類があるvan Laarhoven lens をちょっと理解した
top related