85
Lens/Prism 2016/10/08 Scala Summit 2016

今から始める Lens/Prism

Embed Size (px)

Citation preview

Page 1: 今から始める Lens/Prism

今から始めるLens/Prism

2016/10/08 Scala関西 Summit 2016

Page 2: 今から始める Lens/Prism

お前誰だよ?

Naoki Aoyamaアドテク系 Scala エンジニア

コミッターTwitter: , GitHub: Monocle

@AoiroAoino @aoiroaoino

Page 3: 今から始める Lens/Prism

Lens/Prism ってご存知ですか?

Page 4: 今から始める Lens/Prism

Scala で Lens/Prism を使うモチベーションネストした構造に対する copy メソッドのネスト地獄様々なデータを共通のインターフェースで操作できる非侵入的に外側から追加できる

特定のアーキテクチャに依存しない概念汎用的に使える

などなど

Page 5: 今から始める Lens/Prism
Page 6: 今から始める Lens/Prism

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

Page 7: 今から始める Lens/Prism

実は

余状態コモナド余代数

(Costate Comonad Coalgebra)

Page 8: 今から始める Lens/Prism

getter/setter ってなんだっけ?(広く一般的な意味で)オブジェクト指向言語において、ある field の値を取得するメソッドが getter で、 fieldに値をセットするのが setter。(広く一般的な意味で)クラスのメソッドとして定義され、getFoo、setFoo と 名

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

Page 9: 今から始める Lens/Prism

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

Page 10: 今から始める Lens/Prism

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

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

// getterdef get[S, A]: S => A

Page 11: 今から始める Lens/Prism

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

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

// setterdef set[S, A]: S => A => S

Page 12: 今から始める Lens/Prism

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

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

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

Page 13: 今から始める Lens/Prism

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: 今から始める Lens/Prism

get/set Lensgetter/setter をコンストラクタの引数として与える。

class Lens[S, A]( getter: S => A, setter: S => A => S) {

Page 15: 今から始める Lens/Prism

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)

Page 16: 今から始める Lens/Prism

使い方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: 今から始める Lens/Prism

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: 今から始める Lens/Prism

ネストしたデータが対象の時は?ネストした構造に対する copy メソッドのネスト地獄を

どう解決するか。

Page 19: 今から始める Lens/Prism

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

Page 20: 今から始める Lens/Prism

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)

Page 21: 今から始める Lens/Prism

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

Page 22: 今から始める Lens/Prism

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: 今から始める Lens/Prism

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)

Page 24: 今から始める Lens/Prism

ところで

Page 25: 今から始める Lens/Prism

オブジェクト S に Option 型の field が存在する場合にget メソッドが適切に定義できるだろうか?

Page 26: 今から始める Lens/Prism

A == Option[B] となるような Lens を考えてみる。

class Lens[S, A]( getter: S => A, setter: S => A => S) { // ...}

Page 27: 今から始める Lens/Prism

すると、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 => ??? /// 何�返����� }

/// ...}

Page 28: 今から始める Lens/Prism

ここで、Prism を導入する。

Page 29: 今から始める Lens/Prism

getter の取りうる引数に対してsetter が返すことが可能な値が足りない場合に

Lens だけではカバーしきれない。↓↓↓

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

Page 30: 今から始める 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)}

Page 31: 今から始める Lens/Prism

まずは、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)

Page 32: 今から始める Lens/Prism

一部のユーザーに 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))

Page 33: 今から始める Lens/Prism

_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

Page 34: 今から始める Lens/Prism

とてもダサい!!

Page 35: 今から始める Lens/Prism

_email.get, _some.getOption を直接呼び出しているコードの見通しが悪い

getOption の内部がより複雑だった場合

Page 36: 今から始める Lens/Prism

Lens の時と同じように合成を考えれば良い。

Page 37: 今から始める Lens/Prism

Q: Lens と Prism を合成すると何になる?

LensPrismその他

Page 38: 今から始める Lens/Prism

A: Optional

Page 39: 今から始める Lens/Prism

Optics Hierarchie (Monocle)

ref. Monocle の よりREADME.md

Page 40: 今から始める Lens/Prism

Optics Hierarchie (lens)

ref. lens の よりREADME.md

Page 41: 今から始める Lens/Prism

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]{ // ... }}

Page 42: 今から始める Lens/Prism

Monocle を使って書くと以下の通り。

val user = User(1, "aaa", Some("email"))

(_email composePrism _some).getOption(user)// res: String = email

様々な合成メソッドのエイリアスが記号として提供されている。

(_email ̂<-? _some).getOption(user)// res: String = email

Page 43: 今から始める Lens/Prism

\( 'ω')/ウオオオオオアアアーーーッ!

Page 44: 今から始める Lens/Prism

^|->>

^|-?

^<-?

^|->

^<->

Page 45: 今から始める Lens/Prism

\(՞ةڼ◔)/ウオオオオオアアアーーーッ!?!?!?

Page 46: 今から始める Lens/Prism

ここまでのまとめ

Lens/Prism の概念と使い方を理解したOptics Hierarchie を知った

Page 47: 今から始める Lens/Prism

Lens の種類

Page 48: 今から始める Lens/Prism

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

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

Page 49: 今から始める Lens/Prism

van Laarhoven Lens

Page 50: 今から始める Lens/Prism

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

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

bloglens

Monocle

Page 51: 今から始める Lens/Prism

def Lens[S, A, F[_]: Functor] = S => (A => F[A]) => F[S]

Page 52: 今から始める Lens/Prism

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

Page 53: 今から始める Lens/Prism
Page 54: 今から始める Lens/Prism

閑話休題

Page 55: 今から始める Lens/Prism

traverse 関数はご存知ですか?

def traverse[G[_]: Applicative, A, B](fa: F[A])(f: A => G[B]): G[F[B]]

Page 56: 今から始める Lens/Prism

例えば、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 57: 今から始める Lens/Prism

閑話休題おわり

Page 58: 今から始める Lens/Prism

理解する為に getter/setter を再考してみよう。

Page 59: 今から始める Lens/Prism

setter の再考

Page 60: 今から始める Lens/Prism

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

def setter: S => A => S

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

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

と、改めて定義する。

Page 61: 今から始める Lens/Prism

この型に見覚えない?

Page 62: 今から始める Lens/Prism

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

Page 63: 今から始める Lens/Prism

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

Page 64: 今から始める Lens/Prism

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

Page 65: 今から始める Lens/Prism

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 66: 今から始める Lens/Prism

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

Page 67: 今から始める Lens/Prism

動作確認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)

\( 'ω')/ウオオオオオアアアーーーッ!

Page 68: 今から始める Lens/Prism

getter の再考

Page 69: 今から始める Lens/Prism

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

def getter: S => A

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

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

と、改めて定義する。

Page 70: 今から始める Lens/Prism

この型に見覚えない?

Page 71: 今から始める Lens/Prism

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

Page 72: 今から始める Lens/Prism

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

Page 73: 今から始める Lens/Prism

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

Page 74: 今から始める Lens/Prism

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 75: 今から始める Lens/Prism

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

Page 76: 今から始める Lens/Prism

さて、我々は似たような 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 77: 今から始める Lens/Prism

これで van Laarhoven lens の定義を理解できましたね?

def Lens[S, A, F[_]: Functor] = S => (A => F[A]) => F[S]

Page 78: 今から始める Lens/Prism

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

Page 79: 今から始める Lens/Prism

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

Page 80: 今から始める Lens/Prism

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

Page 81: 今から始める Lens/Prism

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

Page 82: 今から始める Lens/Prism

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 83: 今から始める Lens/Prism

Haskell で van Laarhoven Lens の合成はこう書ける。

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

Page 84: 今から始める Lens/Prism

A: Haskell だと超美しく合成できる...

Page 85: 今から始める Lens/Prism

まとめLens/Prism の概念と使い方を完全マスターしたOptics Hierarchie と呼ばれる階層が存在するLens には種類があるvan Laarhoven lens はやばい