Lens memo
Control.Lensについてのメモ
Control.Lens
について以下のようなことを調べました:
- すぐ忘れてしまうLensによるリストやタプルに対するmapやfoldの方法
- 特に
^..
と^.
,both
やeach
の使い分け - それとどう書けばタプルをfoldした結果になってくれるのか
フロントページの知識だけでなんとかしてみよう。
1. Lensによるmapとfold
前提知識としての型の一覧
Applicative
は「下に降りて行かないMonad
」
Lensの基礎クラス:
Getter
-- 説明省略Setter
-- 説明省略Lens
--Getter
とSetter
を合わせたもの(だっただろうか)Fold
-- Lensの世界でのfoldするメソッド
確認事項:(^.)
フロントページの図から関係ありそうなところを抽出:
(^.) :: Monoid r => s -> Fold s r -> r
(^.) :: Getter s r -> ruto :: (s -> a) -> Getter s a` -- to で一般の関数がLensに持ち上げられる
なので、(^.)
, to
はまあ普通に関数適用と考えてよいだろう。
Prelude Control.Lens> ["0", "12", "345"] ^. to length
3
Prelude Control.Lens> ("0", "12", "345") ^. _2
"12"
知りたいこと1:(^..)
フロントページには出てこないので探しまわることになる。 * Control.Lens.Traversal
かと思うとそこにはない。 * Control.Lens.Operators
だった
その定義は
(^..) :: s -> Getting (Endo [a]) s a -> [a] | infixl 8 |
A convenient infix (flipped) version of [toListOf](https://hackage.haskell.org/package/lens-4.13/docs/Control-Lens-Fold.html#v:toListOf).
なのでtoListOf
の定義を見ると
toListOf :: Getting (Endo [a]) s a -> s -> [a] Source
Extract a list of the targets of a Fold. See also (^..). toList ≡ toListOf folded
(^..) ≡ flip toListOf
Endo
の定義は Data.Monoid にあって
newtype Endo a Source
The monoid of endomorphisms under composition.
とのこと。終対象の終対象?? Endo [a]
の場合は[a]
になるものということか? よくわからないので、いくつか実行してみる。
Prelude Control.Lens> toListOf (to length) ["a", "bb", "ccd", "dd"]
[4] -- 全体の長さ4をリストにして返している
Prelude Control.Lens> toListOf (to (1+)) [8, 9, 0, 1]
<interactive>
Non type-variable argument in the constraint: Num [t]
When checking that ‘it’ has the inferred type
it :: forall t. (Num t, Num [t]) => [[t]] -- [8, 9, 0, 1]はintではない
第2引数全体を渡して結果をリストにくるんでいる。 以下のように全体がタプルでも受け付けて、ただし結果はリストにくるむ:
Prelude Control.Lens> toListOf (to fst) ((1, 3), (4, 6))
[(1,3)] --
さらに確認:
Prelude Control.Lens> toListOf (to length) ("ae", "", "mbr")
→ エラー
Prelude Control.Lens> toListOf (to length) ["ae", "", "mbr"]
[3]
ということでtoListOf
は確認できた。関数適用の結果をリストにくるむだけだ。 (^..)
はこれの演算子バージョンなので、
Prelude Control.Lens> ["ae", "", "mbr"] ^.. (to length)
[3] -- これは全体の長さ
Prelude Control.Lens> ((1,3,0), (3,4,2), (8,3,9)) ^.. _1
[(1,3,0)] -- これは先頭の要素
Prelude Control.Lens> ((1,3,0), (3,4,2)) ^.. _1
[(1,3,0)]
Prelude Control.Lens> ((1,3,0), (3,4,2), (8,3,9), (8,3,9)) ^.. _1
[(1,3,0)]
Prelude Control.Lens> [(1,3,0), (3,4,2), (8,3,9), (8,3,9)] ^.. _1
エラー
ということで(^.)
は、その対象データに対してある関数を適用するもの。 (^..)
は(^.)
の結果をリストでくるむもの。
Prelude Control.Lens> [[1,3,0], [3,4,2], [8,3,9], [8,3,9]] ^. (to length)
4
Prelude Control.Lens> [[1,3,0], [3,4,2], [8,3,9], [8,3,9]] ^.. (to length)
[4]
Prelude Control.Lens> [[1,3,0], [3,4,2], [8,3,9], [8,3,9]] ^. (to concat)
[1,3,0,3,4,2,8,3,9,8,3,9]
Prelude Control.Lens> [[1,3,0], [3,4,2], [8,3,9], [8,3,9]] ^.. (to concat)
[[1,3,0,3,4,2,8,3,9,8,3,9]]
知りたいこと2:folded
フロントページの図から関係ありそうなところを抽出:
folded :: Foldable f => Fold (f a) a
ところがghciで確認すると、folded
は二引数関数となる。
folded :: (Applicative f1, Foldable f, Indexable Int p, Contravariant f1) =>
p a (f1 a) -> f a -> f1 (f a)
これはFold
が関数型だからだ 1 。ghciの出力の方がわかりやすいのでこれで考 えると、どうも、あるコンテナからそれを包む別のコンテナへ形を変えているようだ。Contravariant
が出てくることから、ここで考えているのはデータ型ではなく関数なので、コンテナが深くなるというこ とは、コンテナではなくその中の要素を受付ける関数を構成していることになる。 すなわち、folded
はmap
のLensバージョンと。 さらに、要素単位で計算した結果全てを返すために(^.)
ではなく(^..)
が必要になると。。。
Prelude Control.Lens> [[2,3,4], [5,6,7], [8,9,10], [11,12,13]] ^.. folded . (to sum)
[9,18,27,36]
Prelude Control.Lens> 4b[[2,3,4], [5,6,7], [8,9,10], [11,12,13]] ^. folded . (to sum)
→ エラー :包むコンテナが不明らしい
Prelude Control.Lens> ([2,3,4], [5,6,7], [8,9,10], [11,12,13]) ^.. folded . (to sum)
→ エラー:タプルはFoldableではない
ということがわかれば再帰的に降りていくこともできるようになるでしょう。
Prelude Control.Lens> [[[2,3,4]], [[5,6]], [[8]], [[]]] ^.. folded . to length
[1,1,1,1] -- 引数の中の要素はどれも長さ1のリスト
Prelude Control.Lens> [[[2,3,4]], [[5,6]], [[8]], [[]]] ^.. folded . folded . to length
[3,2,1,0] -- 引数の中の要素の第1要素はどれも長さ1のリスト
Prelude Control.Lens> [[[0,1],[2,3,4]], [[5,6]], [[8]], [[]]] ^.. folded . to length
[2,1,1,1] -- 引数の中の第1要素は長いリストになった
Prelude Control.Lens> [[[0,1],[2,3,4]], [[5,6]], [[8]], [[]]] ^.. folded . folded . to length
[2,3,2,1,0] -- 関数適用の結果は*folded*(*traversed*ではないのか?)される!
つまり単純化して言えばconcatMap
みたいなものだ。folded
で深いところに降りていく一方で、構造を潰してフラットなリストにするということなのでしょう。
- 定義は以下の通り: ↩
Prelude Control.Lens> :i Fold
type role ReifiedFold nominal nominal
newtype ReifiedFold s a = Fold {...} -- Defined in ‘Control.Lens.Reified’type Fold s a =
forall (f :: * -> *).
(Contravariant f, Applicative f) => (a -> f a) -> s -> f s -- Defined in ‘Control.Lens.Type’
2. bothとeachについて
確認事項再掲
Lensの基礎クラス
Getter
-- 説明省略Setter
-- 説明省略Lens
--Getter
とSetter
を合わせたもの(だっただろうか)Fold
-- Lensの世界でのfoldするメソッド(^.) :: Monoid r => s -> Fold s r -> r
または(^.) :: Getter s r -> r
- 簡単に言って、
(^..) = (: []) . (^.)
知りたいこと3:each
each
はフロントページに出てこないので探すと、なんとそのものズバリモジュール になっていた:Control.Lens.each
。定義は以下の通り:
class Each s t a b | s -> a, t -> b, s b -> t, t a -> s where Source
Extract each element of a (potentially monomorphic) container.
Methods
each :: Traversal s t a b
単に要素へのアクセスをするLens?
Control.Lens> ["aa", "", "mbr"] ^. each
"aambr"
Prelude Control.Lens> ["aa", "", "mbr"] ^. each . to id
"aambr"
Prelude Control.Lens> ["aa", "", "mbr"] ^. each . to length
→ エラー: No instance for (Monoid Int) arising from a use of ‘each’
Prelude Control.Lens> ["aa", "", "mbr"] ^. each . to (\n -> [length n])
[2,0,3] -- リストにくるんで`mappend`できるようにしてみた
Prelude Control.Lens> ["aa", "", "mbr"] ^.. each . to (\n -> [length n])
[[2],[0],[3]] -- (^..)だとリストのリストになる
Prelude Control.Lens> ["aa", "", "mbr"] ^.. each . to length
[2,0,3] -- なのでリストでくるむのはやめた
Prelude Control.Lens> ["aa", "", "mbr"] ^.. each . each . to (== 'a')
[True,True,False,False,False]
なるほど、そういうこと。特に難しいことはないようだ。
知りたいこと4:both
次はboth
。定義はControl.Lens.Traversal
both :: Bitraversable r => Traversal (r a a) (r b b) a bclass (Bifunctor t, Bifoldable t) => Bitraversable t where Source
Minimal complete definition either bitraverse or bisequenceA. Bitraversable identifies bifunctorial data structures whose elements can be traversed in order, performing Applicative or Monad actions at each element, and collecting a result structure with the same shape. A definition of traverse must satisfy the following laws:
Bitraversable
は何を言っているのかわからない。traverseできる2要素データ?
Prelude Control.Lens> ("aa", "mbr") ^.. each . to length
[2,3] -- これを元にして
Prelude Control.Lens> ("aa", "mbr") ^.. both . to length
[2,3] -- タプルに対してbothを適用
Prelude Control.Lens> ("aa", "mbr", "third") ^.. both . to length
[3,5] -- 3つ組タプルに対してもbothを適用できる
Prelude Control.Lens> ["aa", "mbr", "third"] ^.. both . to length
→ エラー
Prelude Control.Lens> (("aa", "mbr"), ("third", "")) ^.. both . both . to length
[2,3,5,0] -- 再帰的に降りていける
Prelude Control.Lens> (("aa", "mbr"), ("third", "")) ^. both . both . to (\n -> [length n])
[2,3,5,0] -- モノイドにすれば(^.)でもOK
Prelude Control.Lens> (("aa", "mbr"), ("third", "")) ^. both . both
"aambrthird" -- 文字列はモノイドなので、これで要素の結合になる
なるほど。これも難しいことはないようだ。
3. レンズの内部構造
- 参考: http://solorab.net/blog/2014/12/14/how-to-use-lens-b/
lens
関数とは- getter/setterのふるまいの定義と切り替えの実現方法
レンズ生成関数lens
レンズとはgetterとしてもsetterとしてもふるまえる関数。どうしてこういうことができるのだろう。
レンズは以下の関数lens
(その定義)で作成するのが普通のようだ。
lens :: (s -> a) -> (s -> b -> t) -> Lens s t a b
lens sa sbt afb s = sbt s <$> afb (sa s)
-- | 意味的な名前を与えると
lens getter setter lifter s = getter s <$> lifter (setter s)-- | ちなみにLensの定義は以下の通り
type Lens s t a b = forall f. Functor f => (a -> f b) -> s -> f t
3 <$> Just 4
がJust 3
になり4 <$> Nothing
がNothing
になるように、(<$>)
は一種のスイッチとして考えることができるので、適当なFunctorに持ち上げて左の値を返すか右の値を返すかを選択できればいい。
で何を使えばいい? Maybe
? Either
?
答えはFunctorそのものも切り替えるということでした(だからLens
にはforall f. Functor f
という型制約がついている)。
このlens
で生成されるレンズはこのように自分で定義することもできる(Haskellのlensの使い方 (詳しめ)より引用・簡略化しました)。
-- | 対象データ型
data Foo = Foo { _bar :: Int }-- | barレンズ
-- ここで lens _bar (\s v -> s { _bar = v }) == bar
bar :: forall f. Functor f => (Int -> f Int) -> Foo a -> f (Foo a)
bar f s = (\ b -> s { _bar = b }) <$> f (_bar s)-- | このbarレンズをlensを使って定義すると以下のようになる
bar = lens _bar (\s b -> s { _bar = b })
参照の場合
(^.)
は Control.Lens.Operator
にて以下のように定義されている。(本当はControl.Lens.Getter.hs
。)
(^.) :: s -> Getting a s a -> a -- ここは今回は無視
s ^. l = getConst (l Const s)
Const
はControl.Applicative
で定義されていて
instance Functor (Const m) where
fmap _ (Const v) = Const v
ということで左値は無視されるのでgetterとしてふるまう。1
代入の場合
(.~)
も Control.Lens.Operator
にて以下のように定義されている。(本当はControl.Lens.Setter.hs
。)
(.~) :: ASetter s t a b -> b -> s -> t
(.~) = set-- | setを追いかけると同じファイルにあって
set :: ASetter s t a b -> b -> s -> t
set l b = runIdentity #. l (\_ -> Identity b)
この(#.)
が一体何なのかわからなかったけど、getしたものを無視してb
をsetter
の第2引数に渡して、runIdentity
でIdentity
を剥がすのでフィールドが更新されたデータが返るということになる。
レンズを定義する
If you want to provide lenses and traversals for your own types in your own libraries, then you can do so without incurring a dependency on this (or any other) lens package at all.
(https://hackage.haskell.org/package/lens)
data Foo a = Foo Int Int a-- bar :: Lens' (Foo a) Int
bar :: Functor f => (Int -> f Int) -> Foo a -> f (Foo a)
bar f (Foo a b c) = fmap (\a' -> Foo a' b c) (f a)-- lensを使うと
bar = lens (\(Foo a _ _) -> a) (\(Foo _ b c) a -> Foo a b c)-- quux :: Lens (Foo a) (Foo b) a b
quux :: Functor f => (a -> f b) -> Foo a -> f (Foo b)
quux f (Foo a b c) = fmap (Foo a b) (f c) -- lensを使うと
quux = lens (\(Foo _ _ c) -> c) (\(Foo a b c) x -> Foo a b x)
つまり、対象とするフィールドを取り出してFunctorに包んで開いて、元の位置に戻してやるとレンズになるというふうに覚えればいいようだ。
- (<$>)はData.Functor によれば ”An infix synonym for fmap.” とのこと。 ↩
プリズム生成関数から見るPrismの型
-- | まずlensの復習
Prelude Control.Lens> :t lens
lens :: Functor f => (s -> a) -> (s -> b -> t) -> (a -> f b) -> s -> f t
-- 意味付けするとこんな感じ
lens :: getter -> setter -> (lifter -> data -> result)-- | prismの定義
prism :: (b -> t) -> (s -> Either t a) -> Prism s t a b
prism bt seta = dimap seta (either pure (fmap bt)) . right'
-- これはこう解釈すべき(第1引数はむしろsetter):
prism :: reverseTranslater -> converterInEither -> Prism ...> :i right'
class Profunctor p => Choice (p :: * -> * -> *) where
right' :: p a b -> p (Either c a) (Either c b)
-- Defined in ‘Data.Profunctor’
> :t dimap
dimap :: Profunctor p => (a -> b) -> (c -> d) -> p b c -> p a d
> :t either
either :: (a -> c) -> (b -> c) -> Either a b -> c-- | prismの理解のため、中の合成関数の型を計算して検証してみる
dimap _ _ . right' ::
((a -> b) -> (c -> d) -> p b c -> p a d) . _ :: (p a b -> p (Either c a) (Either c b)
-- { 変数変換して関数合成 }
= (a' -> b') -> (c' -> d') -> p a b -> p a' d') ただし p (Either c a) (Either c b) 〜 p b' c'
-- { 代入: b' ~ Either c a, c' ~ Either c b }
= (a' -> Either c a) -> (Either c b -> d') -> p a b -> p a' d'
-- { prismの型に合わせるため c ~ t }
= (a' -> Either t a) -> (Either t b -> d') -> p a b -> p a' d'
-- { 第2引数 (either pure (fmap bt)) :: Either t b -> d' から、fmap btの返値 :: d' は Functor f => f y と同型でなければならない }
= (a' -> Either t a) -> (Either t b -> f y) -> p a b -> p a' (f y)
-- { またfmap btの引数 :: b はFunctor f => f xの形でなければならない }
= (a' -> Either t a) -> (Either t (f x) -> f y) -> p a (f x) -> p a' (f y)
-- { また fmap bt :: Functor f x => f y と決まったことから、
-- bt :: x -> y でなければならない。prismでの型定義と合わせると
-- x ~ b, y ~ t }
= (a' -> Either t a) -> (Either t (f b) -> f t) -> p a (f b) -> p a' (f t)
-- { 最後に a' ~ s とする }
= (s -> Either t a) -> (Either t (f b) -> f t) -> p a (f b) -> p s (f t)
prismのレベルに戻って考えよう。
- 合成関数の第1引数
(s -> Either t a)
はprism
の第2引数seta
に対応する。そしてこれはlens
の第1引数のgetterに対応する。Eitherで包まれているので値が取り出せる場合とできない場合とを表現することができる。 - 合成関数の第2引数
(Either t (f b) -> f t)
からもprism
の第1引数bt
の型はb -> t
であることがわかる。bt
の関数適用の結果がFunctorに包まれているのでこの値をどう使うか(すなわち代入するかどうか)はFunctor次第となる。こちらはlens
の第2引数のsetterに対応するようだけど、実は入出力の向きが逆。 - そして
prism
によって生成されたプリズムはProfunctor p => p a (f b)
を受け取り、p s (f t)
型を返すはず。 - [検算] これまで隠していた
Prism
の定義は以下の通り。問題なし。
type Prism s t a b = forall p f. (Choice p, Applicative f) => p a (f b) -> p s (f t)
使用例
ドキュメントから例を拾ってみる。
-- | 定義済みのprismの例
Prelude Control.Lens> :t _Left
_Left :: (Applicative f, Choice p) => p a (f b) -> p (Either a c) (f (Either b c))-- 実装の例
-- >>> 5 ^.. nat
-- [5]
-- >>> -8 ^.. nat
-- []
-- >>> 1 & nat .~ 8
-- 8
-- >>> 1 & nat .~ -8
-- エラー (-8はtoInteger :: Natural -> Integer の引数として不適切)
-- >>> -1 & nat .~ 2
-- -1 (-1 はNaturalではない。この時、更新も出来ない!)
nat :: Prism' Integer Natural -- IntegerからNaturalへのプリズム
nat = prism toInteger $ \ i ->
if i < 0 then Left i else Right (fromInteger i)
- prismの第1引数は
Natural -> Integer
なので、これはNatural
をInteger
に対応させるプリズムのはずで、それはnat
の型宣言と一致する。 - getterが成功するときだけsetterも成功するらしい。。。
Choice
ってなんだ? まあ、getter/setterの切替え器なんだろう。
Prismによる条件付getter
-- | プリズムが定義済みのデータ型Eitherに対する例
>>> Left 3 ^.. _Left -- 3はモノイドではないので(^..)を使います。
[3] -- 成功>>> Right 3 ^.. _Left
[] -- 失敗
Prismによる条件付setter
>>> Left 3 & _Left .~ 20
Left 20>>> Left 3 & _Right .~ 20
Left 3
なぜPrismが必要なのか?
条件付き〇〇がなぜ必要なんだろうか。
- getterの返値の型がEitherを使っていることから、適切な値がない場合を表現できる。
- setterにおいても妥当な値が入っている時だけ更新できる。
- 逆向き変換!
Prismの定義例
Lens memo 3: what’s lensで使ったFoo
のbar
をプリズムにしてみよう。
-- | Foo(再掲)
data Foo = Foo { _bar :: Int }-- | barレンズ(再掲)
bar :: forall f. Functor f => (Int -> f Int) -> Foo a -> f (Foo a)
bar f s = (\ b -> s { _bar = b }) <$> f (_bar s)
-- | barレンズをlensを使って再定義すると以下の通り
-- ちなみに lens :: (s -> a) -> (s -> b -> t) -> Lens s t a b
bar = lens _bar (\s b -> s { _bar = b })-- | bar'プリズム
bar' :: Prism' Foo Int -- Foo から(偶数の)Intへ
bar' = prism Foo $ \(Foo x) -> if even x then Right x else Left (Foo 0)-- | ここからbarレンズの実行例
λ> Foo 7 ^.. bar'
[]
λ> Foo 2 ^.. bar
[2]
λ> Foo 2 & bar .~ 10
Foo {_bar = 10}-- | ここからbar'プリズムの実行例
λ> Foo 4 ^.. bar'
[4]
λ> Foo 3 ^.. bar'
[]
λ> Foo 3 & bar' .~ 10
Foo {_bar = 0}-- | ここからreを使ったbar'プリズムの逆向きの実行例
λ> 4 ^. re bar'
Foo {_bar = 4}
λ> 3 ^. re bar'
Foo {_bar = 3} -- 受付けてしまう。偶数型の代わりにIntegerで済ませた影響
λ> (4 & (re bar')) .~ 18
エラー -- これは Foo 4 .~ 18 と同じことだ
λ> (4 ^. (re bar') ) & bar' .~ 30
Foo {_bar = 30}
なるほど。
補足:over
, set
の関係について
(.~) :: ASetter s t a b -> b -> s -> t
Prelude Control.Lens> :t over
over :: ASetter s t a b -> (a -> b) -> s -> t
Prelude Control.Lens> :t set
set :: ASetter s t a b -> b -> s -> t
Prelude Control.Lens> Left "a" & _Left .~ "aa"
Left "aa"
Prelude Control.Lens> over _Left (const "aa") (Left "ab")
Left "aa"
Prelude Control.Lens>