64
Lens 2017/02/26 Scala Matsuri 2017 - day 2

Van laarhoven lens

Embed Size (px)

Citation preview

Page 1: Van laarhoven lens

Lens の基本と基礎2017/02/26 Scala Matsuri 2017 - day 2

Page 2: Van laarhoven lens

お前誰だよ?

Naoki Aoyamaマーベリック株式会社

アドテク系 Scala エンジニアコミッター

Twitter: , GitHub: Monocle

@AoiroAoino @aoiroaoino

Page 3: Van laarhoven lens

Lens ってご存知ですか?

Page 4: Van laarhoven lens

Scala で Lens を使うモチベーションネストした構造に対する copy メソッドのネスト地獄様々なデータを共通のインターフェースで操作できる

非侵入的に外側から追加できる

特定のアーキテクチャに依存しない概念

汎用的に使える

などなど

Page 5: Van laarhoven lens
Page 6: Van laarhoven lens

Lens って何?getter/setter を抽象的にしたものpurely functional reference

Page 7: Van laarhoven lens

実は

余状態コモナド余代数

(Costate Comonad Coalgebra)

Page 8: Van laarhoven lens

getter/setter ってなんだっけ?(広く一般的な意味で)オブジェクト指向言語におい

て、ある field の値を取得するメソッドが getter で、 fieldに値をセットするのが setter。(広く一般的な意味で)クラスのメソッドとして定義され、getFoo、setFoo と 名

付けられる事が多い、おなじみの例のアレ。

Page 9: Van laarhoven lens

getter/setter 再考特定の class に依存しない、汎用的な関数と捉えてみる

Page 10: Van laarhoven lens

getter の仕事とはオブジェクトからある値を取り出す

素直にシグネチャとして表してみると…オブジェクト S からある値 A を取り出すS => A

// getter def get[S, A]: S => A

Page 11: Van laarhoven lens

setter の仕事とはオブジェクトの一部をある値に変更する

素直にシグネチャとして表してみると…オブジェクト S の一部をある値 A に変更するS => A => S

// setter def set[S, A]: S => A => S

Page 12: Van laarhoven lens

あるオブジェクト S の各 field に対して前述のようなgetter/setter が存在した時、 それらを使って汎用的な

get/set メソッドの提供を考える。 ↓↓↓

その仕組みを実現するものを Lens と呼ぶ。

Page 13: Van laarhoven 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))) }

Page 14: Van laarhoven lens

get/set Lensgetter/setter をコンストラクタの引数として与える。class Lens[S, A]( getter: S => A, setter: S => A => S ) {

Page 15: Van laarhoven lens

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 )

Page 16: Van laarhoven lens

使い方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)

Page 17: Van laarhoven lens

Lens LawsLens には満たすべき法則がある。

get/setset(s, get(s)) == s

set/getget(set(s, a)) == a

set/setset(set(s, a1), a2) == set(s, a2)

Page 18: Van laarhoven lens

ネストしたデータが対象の時は?

ネストした構造に対する copy メソッドのネスト地獄をどう解決するか。

Page 19: Van laarhoven lens

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 ) }

Page 20: Van laarhoven lens

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 )

Page 21: Van laarhoven lens

試してみよう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) )

Page 22: Van laarhoven lens

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)

Page 23: Van laarhoven lens

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)

Page 24: Van laarhoven lens

ここまでのまとめ

Lens の基本的な概念を完全に理解した

Page 25: Van laarhoven lens

Lens の種類

Page 26: Van laarhoven lens

Lens にはコンセプトや実装に応じていくつか種類がある

get/set lensget/modify lensIso lensStore Comonad Lensvan Laarhoven lensand so on ...

Page 27: Van laarhoven lens

van Laarhoven Lens

Page 28: Van laarhoven lens

van Laarhoven Lens とは?2009 年 7 月に Twan van Laarhoven さんが書いた のアイディアが元になった Lens で、Haskell の や Scala

の のコンセプトの基礎になってる。

bloglens

Monocle

Page 29: Van laarhoven lens

型のイメージは以下の通り。

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] }

Page 30: Van laarhoven lens

type Lens s a = forall f. Functor f => (a -> f a) -> s -> f s

Page 31: Van laarhoven lens

さて、閑話休題

Page 32: Van laarhoven lens

traverse 関数はご存知ですか?def traverse[G[_]: Applicative, A, B](fa: F[A])(f: A => G[B]): G[F[B]]

Page 33: Van laarhoven lens

例えば、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 を使用しています。

Page 34: Van laarhoven lens

閑話休題おわり

Page 35: Van laarhoven lens

さて、まずは van Laarhoven lens を理解する為にgetter/setter を再考してみましょう。

Page 36: Van laarhoven lens

setter の再考

Page 37: Van laarhoven lens

setter の再考冒頭に出てきた setter の型は以下の通り。

def setter: S => A => S

これは modify メソッドの特殊形と考えられる。 なので、modify の signature を取り入れて

def setter: S => (A => A) => S

と、改めて定義する。

Page 38: Van laarhoven lens

この型に見覚えない?

Page 39: Van laarhoven lens

trait Functor[F[_]] { // F[A] => (A => B) => F[B] def map[A, B](fa: F[A])(f: A => B): F[B] }

Page 40: Van laarhoven lens

つまり、我々は set/modify をするのに各 field に対するFunctor#map が欲しいのだ。

Page 41: Van laarhoven lens

しかし、Functor の instance を class の field 毎にそれぞれ定義することは現実的ではない...

Page 42: Van laarhoven lens

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) }

Page 43: Van laarhoven lens

つまり、この 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)

Page 44: Van laarhoven lens

getter の再考

Page 45: Van laarhoven lens

getter の再考冒頭に出てきた getter の型は以下の通り。

def getter: S => A

取得するだけでなく、関数を適用した結果を返すように

def getter: S => (A => A) => A

と、改めて定義する。

Page 46: Van laarhoven lens

この型に見覚えない?

Page 47: Van laarhoven lens

trait Foldable[F[_]] { // F[A] => (A => B) => B def foldMap[A, B](fa: F[A])(f: A => B)(implicit mb: Monoid[B]): B }

Page 48: Van laarhoven lens

つまり、我々は get をするのに各 field に対するFoldable#foldMap が欲しいのだ。

Page 49: Van laarhoven lens

しかし、Foldable の instance を class の field 毎にそれぞれ定義することは現実的ではない...

Page 50: Van laarhoven lens

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) }

Page 51: Van laarhoven lens

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

Page 52: Van laarhoven lens

試しに 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)) }

Page 53: Van laarhoven lens

本当に動くか試してみよう!!

Page 54: Van laarhoven lens

さて、我々は似たような 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 を要求する型変数に置き換えられそう。

Page 55: Van laarhoven lens

これで van Laarhoven lens の定義を理解できましたね?type Lens[F[_]: Functor, S, A] = S => (A => F[A]) => F[S]

Page 56: Van laarhoven lens

type Lens s a = forall f. Functor f => (a -> f a) -> s -> f s

Page 57: Van laarhoven lens

しかし、ここで一つ疑問が湧きませんか?

Page 58: Van laarhoven lens

Q: van Laarhoven lens はうまく合成できるのだろうか?

Page 59: Van laarhoven lens

Getter も Setter も中身は traverse と同等の関数ですね。なので、代表して traverse の合成を見てみましょう。

Page 60: Van laarhoven lens

しかし、Scala での traverse の(関数合成の意味での)compose は辛いので、 Haskell で試します...

Page 61: Van laarhoven lens

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 などの (.) 演算子に見えませんか?

Page 62: Van laarhoven lens

ちなみに、Haskell で van Laarhoven Lens の合成はこう書ける。

ghci> set (_2 . _1) 42 ("hello",("world","!!!")) ("hello",(42,"!!!"))

Page 63: Van laarhoven lens

A: 合成もできる!

Page 64: Van laarhoven lens

まとめ

Lens の基本を完全に理解したLens には種類があるvan Laarhoven lens をちょっと理解した