# Lenses ## Lensとは要するに - Q.lensって何? - A.第一級getterとsetter - view関数 ``` >>> let atom = Atom {_element = "C", _point = Point{_x = 1, _y = 2}} >>> view (point . x) atom 1.0 ``` - lensはsetとgetを一つの関数に込めている - データ構造でいうとこんな感じ: ``` data Lens = Lens {view :: a -> b ,over :: (b -> b) -> (a -> a) } ``` - これは正確な実装を模してはいない - 最初の勘を養うには役立つ ## Lensの型の仕組み - Q. Lensの型は何? - A. 先の例では次の感じ ``` point :: Lens' Atom Point x :: Lens' Point Double ``` - ```point```lens関数は_pointフィールドにgetしsetするための全ての情報を持つ - ```x```lens関数も同様 - Lens‘の型変数 ``` -- +-- Bigger type -- | -- v Lens' bigger smaller -- ^ -- | -- + -- Smaller type within the bigger type ``` - ※なるほど型の入れ子情報を持たせておくわけだ - 公式にはLens‘は次の型を持つ ``` type Lens' a b = forall f . Functor f => (b -> f b) -> (a -> f a) ``` - 秘密はFunctorインスタンスfの取り扱いにあり。 - 例:```f=Identity```のとき ``` type ASetter' a b = (b -> Identity b) -> (a -> Identity a) -- 次に同値: (b -> b) -> (a -> a) ``` - ※あ、入れ子の内側の型の更新関数を第一引数に渡して、外側の型のデータを渡すと、内側を更新した値が返る関数になってるのか。 - 例:```f = Const b```の場合 ``` type Getting b a b = (b -> Const b b) -> (a -> Const b a) -- 次に同値: (b -> b) -> (a -> b) ``` - ※これは - f:b上の関数 - x:型aの値、フィールドにb型値を持つ - g::(b -> b) -> (a -> b) - ```g f x = f x' where x'::b,xのフィールド``` と同一視できるということか? - 第一引数に```id```を渡すと、この関数は実質```a -> b ```と同じになる - lensはget,set以外にも膨大な機能を持つが、この二つは非常によく使われる。 ## Lensの生成 - Q. lensはどうやって作ればいいの? - A. TemplateHaskellを使ってもよし、自力で定義してもよし - 先の例ではTemplateHaskellを使っている ``` $(makeLenses ''Atom) $(makeLenses ''Point) ``` - これで次のlens値が定義されている: ``` element :: Lens' Atom String point :: Lens' Atom Point x :: Lens' Atom Double y :: Lens' Atom Double ``` - makeLensesを適用すると、フィールド毎にlens値が定義される - 定義した射影関数から_を除去したものがLens値の名前 - ※名前の衝突を防ぐためにオリジナルには_をつけさせるのだと予想 - TemplateHaskellを使わない場合 - lens関数を使う ``` lens :: (a -> b) -> (a -> b -> a) -> Lens' a b ``` - 第一引数はgetterで、aの成分からb値を取り出す射影 - 第二引数はsetterで、a値のb値成分を更新する関数 - 例:point関数をlens関数で作る ``` point :: Lens' Atom Point point = lens _point (\atom newPoint -> atom {_point = newPoint}) ``` - ※レコード構文なら射影を名指しすればデフォでも特定の値を更新できるのか?知らなかった。 - lensは別にlensライブラリを使わなくても利用できる概念 - 単に、Functor型に基づく高階関数 - point関数は次のように定義しても同じ動きをする: ``` point :: Functor f => (Point -> f Point) -> Atom -> f Atom point k atom = fmap (\newPoint -> atom{ _point = newPoint }) (k (_point atom)) ``` - このように、fmap関数だけ使ってlensの仕組みは自作できる - ※Genericsが捗りそうな案件 ## Lensの合成 - Q. lens同士を合成するには? - A. いつもの関数合成関数```(.)```がそのまま使える - ```type Lens' a b :: forall f . Functor f => (b -> f b) -> (a -> f a)``` - ```(.) :: Lens' a b -> Lens' b c -> Lens' a c``` - Lens'は高階関数型のシノニムであることを思い出すべし ``` (.) :: Functor f => (b -> f b) -> (a -> f a) -> (c -> f c) -> (b -> f b) -> (c -> f c) -> (a -> f a) ``` - 先の例では次のようにしていた: ``` point :: Lens' Atom Point x :: Lens' Point Double point . x :: Lens' Atom Double ``` - ```point . x```により、Atom型のx値をget,setできる ``` view (point . x) :: Atom -> Double over (point . x) :: (Double -> Double) -> (Atom -> Atom) ``` ## Lensの使い方 - Q. lensをどうやって使えば良い? - A. view,set,over関数を使う ``` view :: Lens' a b -> a -> b over :: Lens' a b -> (b -> b) -> a -> a set :: Lebs' a b -> b -> a -> a set lens b = over lens (\_ -> b) ``` - view,overが最も基本的な関数 - setはoverの部分適用 - view,overはLens上で分配する ``` view (lens1 . lens2) = (view lens1) . (view lens2) view id = id over (lens1 . lens2) = (over lens1) . (over lens2) over id = id ``` ## 締め - Q. 他に知るべきことは? - A. だいたいこのくらい。 - 9割の使い道は - lensを定義し - 合成し - 消費する こと - これ以降は応用的な技巧と性質の解説