Van laarhoven lens

Preview:

Citation preview

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 をちょっと理解した

Recommended