Lens memo

Control.Lensについてのメモ

Control.Lensについて以下のようなことを調べました:

  • すぐ忘れてしまうLensによるリストやタプルに対するmapやfoldの方法
  • 特に^..^., botheachの使い分け
  • それとどう書けばタプルをfoldした結果になってくれるのか

フロントページの知識だけでなんとかしてみよう。

1. Lensによるmapとfold

前提知識としての型の一覧

  1. Applicativeは「下に降りて行かないMonad

Lensの基礎クラス:

  1. Getter-- 説明省略
  2. Setter-- 説明省略
  3. Lens -- GetterSetter を合わせたもの(だっただろうか)
  4. Fold -- Lensの世界でのfoldするメソッド

確認事項:(^.)

フロントページの図から関係ありそうなところを抽出:

(^.) :: Monoid r => s -> Fold s r -> r
(^.) :: Getter s r -> r
uto :: (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が出てくることから、ここで考えているのはデータ型ではなく関数なので、コンテナが深くなるというこ とは、コンテナではなくその中の要素を受付ける関数を構成していることになる。 すなわち、foldedmapの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で深いところに降りていく一方で、構造を潰してフラットなリストにするということなのでしょう。


  1. 定義は以下の通り:
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の基礎クラス

  1. Getter-- 説明省略
  2. Setter-- 説明省略
  3. Lens -- GetterSetter を合わせたもの(だっただろうか)
  4. Fold -- Lensの世界でのfoldするメソッド
  5. (^.) :: Monoid r => s -> Fold s r -> r または (^.) :: Getter s r -> r
  6. 簡単に言って、(^..) = (: []) . (^.)

知りたいこと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 b
class (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. レンズの内部構造

レンズ生成関数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 4Just 3になり4 <$> NothingNothingになるように、(<$>)は一種のスイッチとして考えることができるので、適当なFunctorに持ち上げて左の値を返すか右の値を返すかを選択できればいい。

で何を使えばいい? MaybeEither

答えは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)

ConstControl.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したものを無視してbsetterの第2引数に渡して、runIdentityIdentityを剥がすのでフィールドが更新されたデータが返るということになる。

レンズを定義する

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に包んで開いて、元の位置に戻してやるとレンズになるというふうに覚えればいいようだ。


  1. (<$>)は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なので、これはNaturalIntegerに対応させるプリズムのはずで、それは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で使ったFoobarをプリズムにしてみよう。

-- | 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>
Show your support

Clapping shows how much you appreciated Shuji Narazaki’s story.