Lens memo

Control.Lensについてのメモ

Shuji Narazaki
text-is-saved
24 min readApr 29, 2017

--

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するメソッド

確認事項:(^.)

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

なので、(^.), toはまあ普通に関数適用と考えてよいだろう。

知りたいこと1:(^..)

フロントページには出てこないので探しまわることになる。 * Control.Lens.Traversal かと思うとそこにはない。 * Control.Lens.Operatorsだった

その定義は

なのでtoListOfの定義を見ると

Endoの定義は Data.Monoid にあって

とのこと。終対象の終対象?? Endo [a]の場合は[a]になるものということか? よくわからないので、いくつか実行してみる。

第2引数全体を渡して結果をリストにくるんでいる。 以下のように全体がタプルでも受け付けて、ただし結果はリストにくるむ:

さらに確認:

ということでtoListOfは確認できた。関数適用の結果をリストにくるむだけだ。 (^..)はこれの演算子バージョンなので、

ということで(^.)は、その対象データに対してある関数を適用するもの。 (^..)(^.)の結果をリストでくるむもの。

知りたいこと2:folded

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

ところがghciで確認すると、foldedは二引数関数となる。

これはFoldが関数型だからだ 1 。ghciの出力の方がわかりやすいのでこれで考 えると、どうも、あるコンテナからそれを包む別のコンテナへ形を変えているようだ。Contravariantが出てくることから、ここで考えているのはデータ型ではなく関数なので、コンテナが深くなるというこ とは、コンテナではなくその中の要素を受付ける関数を構成していることになる。 すなわち、foldedmapのLensバージョンと。 さらに、要素単位で計算した結果全てを返すために(^.)ではなく(^..)が必要になると。。。

ということがわかれば再帰的に降りていくこともできるようになるでしょう。

つまり単純化して言えばconcatMapみたいなものだ。foldedで深いところに降りていく一方で、構造を潰してフラットなリストにするということなのでしょう。

  1. 定義は以下の通り:

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。定義は以下の通り:

単に要素へのアクセスをするLens?

なるほど、そういうこと。特に難しいことはないようだ。

知りたいこと4:both

次はboth。定義はControl.Lens.Traversal

Bitraversableは何を言っているのかわからない。traverseできる2要素データ?

なるほど。これも難しいことはないようだ。

3. レンズの内部構造

レンズ生成関数lens

レンズとはgetterとしてもsetterとしてもふるまえる関数。どうしてこういうことができるのだろう。

レンズは以下の関数lensその定義)で作成するのが普通のようだ。

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

で何を使えばいい? MaybeEither

答えはFunctorそのものも切り替えるということでした(だからLensにはforall f. Functor fという型制約がついている)。

このlensで生成されるレンズはこのように自分で定義することもできる(Haskellのlensの使い方 (詳しめ)より引用・簡略化しました)。

参照の場合

(^.)Control.Lens.Operator にて以下のように定義されている。(本当はControl.Lens.Getter.hs。)

ConstControl.Applicativeで定義されていて

ということで左値は無視されるのでgetterとしてふるまう。1

代入の場合

(.~)Control.Lens.Operator にて以下のように定義されている。(本当はControl.Lens.Setter.hs。)

この(#.)が一体何なのかわからなかったけど、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)

つまり、対象とするフィールドを取り出してFunctorに包んで開いて、元の位置に戻してやるとレンズになるというふうに覚えればいいようだ。

  1. (<$>)はData.Functor によれば ”An infix synonym for fmap.” とのこと。

プリズム生成関数から見るPrismの型

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の定義は以下の通り。問題なし。

使用例

ドキュメントから例を拾ってみる。

  • prismの第1引数はNatural -> Integerなので、これはNaturalIntegerに対応させるプリズムのはずで、それはnatの型宣言と一致する。
  • getterが成功するときだけsetterも成功するらしい。。。
  • Choiceってなんだ? まあ、getter/setterの切替え器なんだろう。

Prismによる条件付getter

Prismによる条件付setter

なぜPrismが必要なのか?

条件付き〇〇がなぜ必要なんだろうか。

  • getterの返値の型がEitherを使っていることから、適切な値がない場合を表現できる。
  • setterにおいても妥当な値が入っている時だけ更新できる。
  • 逆向き変換!

Prismの定義例

Lens memo 3: what’s lensで使ったFoobarをプリズムにしてみよう。

なるほど。

補足:over, setの関係について

--

--

Shuji Narazaki
text-is-saved

Studying SAT solvers and symbolic computation (type and logic). Being into 円城塔, Greg Egan, Stephen Colbert, 酒見賢一, say a Sci. Fi. person.