Lens型
code:hs
type Lens s t a b = forall f. Functor f => (a -> f b) -> s -> f t
s,tが全体の型
a,bが部分の型
全体の型を変更する可能性を含めて一般化しているので4つも型引数が必要になってるmrsekut.icon
その可能性を排除すると以下のようなシンプル版を定義できる ref code:hs
type Lens' s a = Lens s s a a
sが全体の型
e.g. (x,y)
aが部分の型
e.g. (x,y)に対するx
この型の表す意味と、この定義の意味
Lensという概念はそもそも、getterとsetterを一緒にしたもの getterは「あるデータ」から「その一部」を取ってくる
setterは「その一部」を変化させて、新しい「あるデータ構造」を得る
この2つを同時に定義したものがLens型で、composabilyなどの良い性質を備えている
上記のような定義になることを理解するためには順を追っていく必要がある
その内容を雑に書くと
1段階目
直観的な定義
getterとsetterを組にして定義する
aが全体の型、bが部分の型
(>-)はLens同士を組み合わせる関数
code:lv1.hs
data Lens a b
= Lens { get :: a -> b -- view
, set :: b -> a -> a -- over
}
(>-) :: Lens a b -> Lens b c -> Lens a c
la >- lb = Lens (get lb . get la) $ \part whole ->
set la (set lb part (get la whole)) whole
2段階目
Store型などの概念を導入する
(>-)は割とやってることをそのまま書き下した感じになっている
code:lb2.hs
type Lens a b = a -> Store b a
data Store b a = Store b (b -> a)
(>-) :: Lens a b -> Lens b c -> Lens a c
(la >- lb) a = let Store partB holeBA = la a
Store partC holeCB = lb partB
holeCA = holeBA . holeCB
in Store partC holeCA
3段階目
上記ではStore型に依存してしまっているのでより抽象的な構造にする
以下のようにすることでFunctorであれば何でも良い、というようになった
Coalgebraを使うことで、合成もただの関数合成(.)と同じになっている
code:lv3.hs
type Lens a b = Functor f => Coalg f b -> Coalg f a
type Coalg f x = x -> f x
(>-) :: Lens a b -> Lens b c -> Lens a c
(>-) = (.)
これで上述のLens' s aが完成している
4段階目
Lensを通して型を変換できるように、より一般的な定義に変更
code:lv4.hs
type Lens s t a b = Functor f => (a -> f b) -> (s -> f t)
(>-) :: Lens s t a b -> Lens a b c d -> Lens s t c d
(>-) = (.)
なんでこの1つの型で、getter/setterを表現できるのかの解説
functorにIdentityを適用すればsetterになり、Const bを適用すればgetterになる
すごすぎん?mrsekut.icon