SlideShare a Scribd company logo
今から始める
Lens/Prism
2016/10/08 Scala関西Summit 2016
お前誰だよ?
Naoki Aoyama
アドテク系Scala エンジニア
コミッター
Twitter: , GitHub:
Monocle
@AoiroAoino @aoiroaoino
Lens/Prism ってご存知ですか?
Scala でLens/Prism を使うモチベーション
ネストした構造に対するcopy メソッドのネスト地獄
様々なデータを共通のインターフェースで操作できる
非侵入的に外側から追加できる
特定のアーキテクチャに依存しない概念
汎用的に使える
などなど
今から始める Lens/Prism
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 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)))
}
get/set Lens
getter/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 対 Lens
val _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 Laws
Lens には満たすべき法則がある。
get/set
set(s, get(s)) == s
set/get
get(set(s, a)) == a
set/set
set(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 対 Lens
val _body = ...
// Message#user 対 Lens
val _user: Lens[Message, User] = new Lens(
_.user,
message => newUser => message.copy(user = newUser)
)
Before
val 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 er
val 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 と呼ぶ。
Prism
class 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 対 Prism
def _some[A] = new Prism[Option[A], A](
{ case Some(a) => Some(a); case None => None },
Some.apply
)
// None 対 Prism
def _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 対 Prism
val _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 を合成すると何になる?
Lens
Prism
その他
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 lens
get/modify lens
Iso lens
Store Comonad Lens
van Laarhoven lens
and so on ...
van Laarhoven Lens
van Laarhoven Lens とは?
2009 年7 月にTwan van Laarhoven さんが書いた の
アイディアが元になったLens で、Haskell の やScala
の のコンセプトの基礎になってる。
blog
lens
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
今から始める Lens/Prism
閑話休題
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) => S
def 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 => S
def 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 => A
def 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 はやばい

More Related Content

今から始める Lens/Prism