16. ネストしたタプルから要素を取り出す
_1 ~ _9 は (.) で関数合成しても同じ型
ghci> :t _1
_1
:: (Functor f, Field1 s t a b, Indexable Int p) =>
p a (f b) -> s -> f t
ghci> :t _1._2
_1._2
:: (Functor f, Field2 s1 t1 a b, Field1 s t s1 t1,
Indexable Int p) =>
p a (f b) -> s -> f t
_1 ~ _9 を (.) で合成して、ネストした
複雑なタプルの内側の値をピンポイントで取り出し
(100, 200, (310, (321, 322, 323, 999, 325), 330), 400)^._3._2._4
-- => 999
17. Lens でタプルの値を変更
(.~) に _1 ~ _9 と、任意の値を適用
ghci> :t _2.~"Foo"
_2.~"Foo" :: Field2 s t a [Char] => s -> t
要素が2つ以上のタプルは
Field2 型クラス s のインスタンス
ghci> :i Field2
class Field2 s t a b | s -> a, t -> b, s b -> t, t a -> s where
_2 :: (Indexable Int p, Functor f) => p a (f b) -> s -> f t
-- Defined in `Control.Lens.Tuple'
...
-- Defined in `Control.Lens.Tuple'
instance Field2 (a, b, c) (a, b', c) b b'
-- Defined in `Control.Lens.Tuple'
instance Field2 (a, b) (a, b') b b'
-- Defined in `Control.Lens.Tuple'
23. Point 型 /Line 型を作る
次のような型を作る
data Point = Point {
x :: Int,
y :: Int
} deriving (Show, Eq)
data Line = Line {
startPoint :: Point,
endPoint :: Point
} deriving (Show, Eq)
次の値を例に色々考えてみよう
sampleLine = Line {
startPoint = Point { x = 100, y = 150 },
endPoint = Point { x = 200, y = 250 }
}
24. Point 単位の操作は簡単
取得
startPoint sampleLine
-- => Point {x = 100, y = 150}
endPoint sampleLine
-- => Point {x = 200, y = 250}
置き換え
SampleLine {
endPoint = Point { x = 1000, y = 1500 }}
25. では、座標単位の操作は?
取得は関数合成を使えば良い
x . endPoint $ sampleLine -- => 200
置き換えは・・・いまいち分りづらい
sampleLine {
endPoint = (endPoint sampleLine) { x = 999 } }
-- => Line {
startPoint = Point {x = 100, y = 150},
endPoint = Point {x = 999, y = 250}}
26. では、座標単位の操作は?
取得は関数合成を使えば良い
x . endPoint $ sampleLine -- => 200
置き換えは・・・いまいち分りづらい
よし、 Lens を使おう!
sampleLine {
endPoint = (endPoint sampleLine) { x = 999 } }
-- => Line {
startPoint = Point {x = 100, y = 150},
endPoint = Point {x = 999, y = 250}}
27. Point 型 /Line 型を Lens にする
フィールド名の前に” _” を付加し
『 makeLenses '' 型名』 と記述する
data Point = Point {
_x :: Int,
_y :: Int
} deriving (Show, Eq)
makeLenses ''Point
data Line = Line {
_startPoint :: Point,
_endPoint :: Point
} deriving (Show, Eq)
makeLenses ''Line
※ コンパイルのためには GHC 拡張の TemplateHaskell を
有効にしておく必要がある
32. Setter を作ろう
単純に 2 要素のタプルの 2 つめの要素を任意の
値に置き換える関数を考えると、次のような型にな
る
f :: a -> (b, c) -> (b, a)
これだけではつまらないので、一つ目の引数を関
数で取るようにする
f :: (a -> b) -> (c, a) -> (c, b)
33. Setter を作ろう
単純に 2 要素のタプルの 2 つめの要素を任意の
値に置き換える関数を考えると、次のような型にな
る
f :: a -> (b, c) -> (b, a)
値を x に置き換えたい場合は
const x を適用すれば良い
これだけではつまらないので、一つ目の引数を関
数で取るようにする
f :: (a -> b) -> (c, a) -> (c, b)
34. ところで
この型、何かと似てない?
f :: (a -> b) -> (c, a) -> (c, b)
fmap :: Functor f => (a -> b) -> f a -> f b
とそっくり・・・
35. ところで
この型、何かと似てない?
f :: (a -> b) -> (c, a) -> (c, b)
※ 衆知のとおり、 2 値のタプルは Functor になっ
ていて、次のような事ができる
fmap (*2) ("Hey!", 5) -- => ("Hey!",10)
しかし Functor では一つ目の要素は操作できない
さて、どうしよう?
36. fmap のもうひとつの実装
Data.Traversable で定義されている Traversable
型クラスで、次の型を持つ traverse 関数が定義さ
れている
traverse :: Applicative f => (a -> f b) -> t a -> f (t b)
同モジュールの fmapDefault 関数は、 traverse 関
数を用いた fmap の別実装
fmapDefault :: Traversable t => (a -> b) -> t a -> t b
fmapDefault f = getId . traverse (Id . f)
37. fmap のもうひとつの実装
Data.Traversable で定義されている Traversable
型クラスで、次の型を持つ traverse 関数が定義さ
れているData.Functor.Identity の定義に同じ
Id は
newtype Id a = Id { getId :: a }
traverse :: Applicative f => (a -> f b) -> t a -> f (t b)
Functor と Applicative のインスンタンス
同モジュールの fmapDefault 関数は、 traverse 関
数を用いた fmap の別実装
fmapDefault :: Traversable t => (a -> b) -> t a -> t b
fmapDefault f = getId . traverse (Id . f)
38. fmapDefault の
動作を決めるのは traverse 関数
なら、 traverse を別の関数と差し替えれば別の動
きをするんじゃなイカ?というわけで ...
fmapDefault から traverse を外出しした over 関数
を定義すると、次のような型になる
over
:: ((a1 -> Id b) -> a -> Id c) -> (a1 -> b) -> a -> c
over l f = getId . l (Id . f)
ここで、第一引数の型に対し Setter という別名を
付けよう
type Setter s t a b = (a -> Id b) -> s -> Id t
39. fmapDefault の
動作を決めるのは traverse 関数
なら、 traverse を別の関数と差し替えれば別の動
きをするんじゃなイカ?というわけで ...
fmapDefault から traverse を外出しした over 関数
を定義すると、次のような型になる
over
:: Setter a c a1 b -> (a1 -> b) -> a -> c
over l f = getId . l (Id . f)
ここで、第一引数の型に対し Setter という別名を
付けよう
type Setter s t a b = (a -> Id b) -> s -> Id t
40. fmapDefault の
動作を決めるのは traverse 関数
なら、 traverse を別の関数と差し替えれば別の動
きをするんじゃなイカ?というわけで ...
これにより、 over の型がこう書ける
fmapDefault から traverse を外出しした over 関数
を定義すると、次のような型になる
over
:: Setter a c a1 b -> (a1 -> b) -> a -> c
over l f = getId . l (Id . f)
ここで、第一引数の型に対し Setter という別名を
付けよう
type Setter s t a b = (a -> Id b) -> s -> Id t
41. fmapDefault の
動作を決めるのは traverse 関数
なら、 traverse を別の関数と差し替えれば別の動
きをするんじゃなイカ?というわけで ...
fmapDefault から traverse を外出しした over 関数
を定義すると、次のような型になる
over
:: Setter a c a1 b -> (a1 -> b) -> a -> c
over l f = getId . l (Id . f)
ここで、第一引数の型に対し Setter という別名を
付けよう
type Setter s t a b = (a -> Id b) -> s -> Id t
42. fmapDefault の
動作を決めるのは traverse 関数
なら、 traverse を別の関数と差し替えれば別の動
きをするんじゃなイカ?というわけで ...
fmapDefault から traverse を外出しした over 関数
を定義すると、次のような型になる
over
:: Setter a c a1 b -> (a1 -> b) -> a -> c
over l f = getId . l (Id . f)
ここで、第一引数の型に対し Setter という別名を
当然、 traverse 関数を適用すれば
付けよう fmapDefault と同値になる
さらに何かしら Setter 型の関数を引数に取る事により
type Setter s t a b = (a -> Id b) -> s -> Id t
fmap と似た別の関数を得る事ができる
43. _1, _2 を実装するには?
最終的に欲しい型
Over _1 :: (a -> b) -> (a, v) -> (b, v)
over の型を読み替え
Over :: Setter (a, v) (b, v) a b
-> (a -> b) -> (a, v) -> (b, v)
_1 の型
_1 :: Setter (a, v) (b, v) a b
-- つまり
_1 :: (a -> Id b) -> (a, v) -> Id (b, v)
44. 実際にやってみる
導きだした型を満足させるよう _1, _2 を実装
_1 :: Setter (a, v) (b, v) a b
_1 f (x, y) = Id (getId . f $ x, y)
_2 :: Setter (v, a) (v, b) a b
_2 f (x, y) = Id (x, getId . f $ y)
任意の要素に fmap できるようになる!
(over _1) (*2) (50, 50) -- => (100,50)
(over _2) (*2) (50, 50) -- => (50,100)
45. こうなれば後は簡単
(.~) は次のようにして簡単に再実装できる
(.~) :: Setter s t a b -> b -> s -> t
a .~ v = over a (const v)
Lens と同じ書き方で要素を変更できるようになる
_1.~999 $ (1, 2) => (999,2)
_2.~999 $ (1, 2) => (1,999)
46. それじゃぁ次は Getter だ!
2 値のタプルからの値の取得は次のような型をイ
メージできる
f :: (a, b) -> b
単に取り出すだけでなく、何か関数を適用して返す
ようにしてみると・・・
f :: (a -> b) -> (c, a) -> b
これは Data.Foldable で定義されている foldMap
関数とそっくり
foldMap
:: (Foldable t, Monoid m) => (a -> m) -> t a -> m
47. Traversable の foldMapDefault
FoldMapDefault の定義が Data.Traversable
に!
foldMapDefault
:: (Traversable t, Monoid m) => (a -> m) -> t a -> m
foldMapDefault f = getConst . traverse (Const . f)
Const は Data.Functor.Constant に定義
newtype Const a b = Const {getConst :: a}
Foldable や Applicative 等のインスンタンス
48. 同じようにして traverse を外に出す
foldMapDefault の実装から traverse を取り出し
foldMapOf 関数を定義
foldMapOf
:: ((a1 -> Const b1 b2) -> a -> Const c b)
-> (a1 -> b1) -> a -> c
foldMapOf l f = getConst . l (Const . f)
第一引数の関数の型に別名を付けてみる
type Getting r s a =
(a -> Const r a) -> s -> Const r s
49. アクセサの定義、
foldMapOf への適用
改めて、 2 値のタプルに対する _1, _2 を次のよう
に定義
_1 :: Getting r (a, s) a
_1 f (x, _) = Const (getConst . f $ x)
_2 :: Getting r (s, a) a
_2 f (_, y) = Const (getConst . f $ y)
foldMapOf と組み合わせて任意の場所の要素を
foldMap
(foldMapOf _1) (*2) (100, 1000) -- => 200
(foldMapOf _2) (*2) (100, 1000) -- => 2000
50. (^.) の実装も超簡単
(^.) :: s -> Getting a s a -> a
v ^. l = (foldMapOf l) id v
値をそのまま取り出したいのだから
引数に対して何もしない関数 id :: a -> a
を、適用してやれば良い
(111, 222)^._1 -- => 111
(111, 222)^._2 -- => 222
51. Setter と Getting
どちらも traverse 関数を元に定義された型なのだ
から、揃える事はできないだろうか?
type Getting r s a =
(a -> Const r a) -> s -> Const r s
type Setter s t a b =
(a -> Id b) -> s -> Id t
Getting の型変数を Setter に合わせて変えてみる
type Getting s t a b = forall m .Monoid m =>
(a -> Const m b) -> s -> Const m t
type Setter s t a b =
(a -> Id b) -> s -> Id t
52. Setter と Getting
どちらも traverse 関数を元に定義された型なのだ
から、揃える事はできないだろうか?
type Getting r s a =
型定義に登場しない型変数 m
(a -> Const r a) -> s -> Const r s
type Setter s t a b =
コンパイルのため、 FoldMap の実装に合せ
(a -> Id b) -> s -> Id t
Monoid を要求するようにしておく
でもあまり嬉しくない制約
Getting の型変数を Setter に合わせて変えてみる
type Getting s t a b = forall m .Monoid m =>
(a -> Const m b) -> s -> Const m t
type Setter s t a b =
(a -> Id b) -> s -> Id t
53. Id も Const も Functor !
従って、次の赤字の部分は、 Functor を要求する
型変数に置き換えることができる
type Getting s t a b = forall m .Monoid m =>
(a -> Const m b) -> s -> Const m t
type Setter s t a b =
(a -> Id b) -> s -> Id t
これで型宣言も一つに纏められる
しかも Getter の Monoid も消えた!やったね!
type Lens s t a b = forall f .Functor f =>
(a -> f b) -> s -> f t
54. _1, _2 を作り替える
後は _1 と _2 を、それぞれ Lens 型に合うように実
装
_1 :: Lens (a, v) (b, v) a b
_1 f (x, y) = fmap (x' -> (x', y)) (f x)
_2 :: Lens (v, a) (v, b) a b
_2 f (x, y) = fmap (y' -> (x, y')) (f y)
ちゃんと使えるかどうか確認・・・バッチリ!
(100, 200)^._1 -- => 100
_1.~999 $ (100, 200) -- => (999,200)
55. traverse. traverse
traverse 関数同士を関数合成するとこうなる
traverse
:: (Control.Applicative.Applicative f, Traversable t) =>
(a -> f b) -> t a -> f (t b)
traverse.traverse
:: (Control.Applicative.Applicative f, Traversable t,
Traversable t1) =>
(a -> f b) -> t (t1 a) -> f (t (t1 b))
traverse.traverse.traverse
:: (Control.Applicative.Applicative f, Traversable t,
Traversable t1, Traversable t2) =>
(a -> f b) -> t (t1 (t2 a)) -> f (t (t1 (t2 b)))
合成しても性質が維持される!
56. Lnes 型の関数は traverse と同じ型
なら _1 や _2 も同じ性質を持っているはず・・・
_2 :: Functor f =>
(a -> f b) -> (v, a) -> f (v, b)
_2._2 :: Functor f =>
(a -> f b) -> (v, (v1, a)) -> f (v, (v1, b))
_2._2._2 :: Functor f =>
(a -> f b) -> (v, (v1, (v2, a))) -> f (v, (v1, (v2, b)))
Lens が関数合成して使えるのは
型を見れば当然の事だった!!
65. (.=) と use 関数
どちらも型クラス制約に MonadState クラスが含ま
れている。
状態系のモナドと組み合わせて使う関数。
ghci> :t (.=)
(.=) :: MonadState s m => ASetter s s a b -> b -> m ()
ghci> :t use
use
:: MonadState s m =>
Getting a s t a b -> m a
66. (.=) と use 関数
(.=) や use の簡単な例:
sample :: State Line ()
sample = do
-- (.=) で状態に代入
startPoint .= Point { _x = 100, _y = 200 }
endPoint.x .= 300
-- 状態から値を取り出し
sp <- use startPoint
epx <- use $ endPoint.x
return ()
67. 各 Setter 演算子の
MonadState バージョン
何処かの言語で見たような書き方ができる
sample2 = do
v %= (*100) --over
v += 10 -- 加算
v -= 10 -- 減算
v *= 10 -- 乗算
v //= 10 -- 除算
b ||= True --OR
b &&= True --AND
※ 「状態」に対してかなりやりたい放題できるように
なるので乱用注意!