Lens型
hstype Lens s t a b = forall f. Functor f => (a -> f b) -> s -> f t
s
, t
が全体の型
a
, b
が部分の型
全体の型を変更する可能性を含めて一般化しているので4つも型引数が必要になってる

その可能性を排除すると以下のようなシンプル版を定義できる
refhstype 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同士を組み合わせる関数
lv1.hsdata 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型などの概念を導入する
(>-)
は割とやってることをそのまま書き下した感じになっている
lb2.hstype 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を使うことで、合成もただの関数合成 (.)
と同じになっている
lv3.hstype 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を通して型を変換できるように、より一般的な定義に変更
lv4.hstype 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になる
すごすぎん?
