lensではオプティクスと呼ばれる様々な構造が定義されている。これらの関係を把握していれば、ドキュメントから欲しいものを見つけるのが楽になる。この記事では弱い順にオプティックの数々を紹介していく。
Fold
type Fold s a = forall f. (Applicative f, Contravariant f) => (a -> f a) -> s -> f s
Contravariantがついているのでわかりにくいが、これは本質的に以下の型と等価だ。mappend
は*>
、mempty
はfmap absurd $ contramap absurd $ pure ()
に相当する。
type Fold s a = forall r. (Monoid r) => (a -> r) -> s -> r
で、これは何かと言えば、foldMap :: Foldable f => (a -> r) -> f a -> r
の一般化そのものだ。foldMap
はf a
の要素a
を畳み込むが、Fold s a
はs
の中のa
を畳み込む。当然、Foldable
でできることはFold
でもできる。そのあたりはモノイドと継続渡しの道具箱で触れている。
FoldはそのままではfoldMapの一般化としては使いにくいため、Control.Lens.Foldモジュールでユーティリティが定義されている。Foldable
のメソッドとしてfoo
があったとき、Foldable
の代わりにFold
を受け取るfooOf
という関数が提供されている。
Getter
Getterはより強く、Functorしか要求しない。
type Getter s a = forall f. (Functor f, Contravariant f) => (a -> f a) -> s -> f s
こちらは、Applicativeに由来するモノイドの力を必要としないため、forall r. (a -> r) -> s -> r
という関数と等価になる。これはs -> a
という関数を継続渡しスタイルにしたものだ。したがって、Getter s a
はs -> a
と同じものとして見てよい。
view :: ((a -> Const a a) -> s -> Const a s) -> s -> a
view l = getConst . l Const
Setter
FoldやGetterと独立した概念としてSetterがある。ASetter
はSetter
型の特殊な場合であるが、本質的な差はない。
type ASetter s t a b = (a -> Identity b) -> s -> Identity t
over :: ASetter s t a b -> (a -> b) -> s -> t
over l f = runIdentity . l (Identity . f)
こちらは(a -> b) -> (s -> t)
と等価だ。セッターとして使えるが、セットした際の型の変化を許すようになっている。
Traversal
FoldがfoldMap
の一般化だったのに対し、TraversalはTraversable
クラスのメソッドtraverse :: (Traversable t, Applicative f) => (a -> f b) -> t a -> f (t b)の一般化になっている。
type Traversal s t a b = forall f. Applicative f => (a -> f b) -> s -> f t
そのため、traverse
やmapM
の代わりを作る感覚でTraversal
を定義できる。例えば、リストの偶数番目の要素に対するtraverseEven
は以下のようになる。
traverseEven :: Traversal [a] [a] a a
traverseEven f (x:y:xs) = (:) <$> f x <*> fmap (y:) (elementsEven f xs)
traverseEven _ xs = pure xs
Traversal
はFold
であり、Setter
でもあるのが特に重要である。例えばover elementsEven (*3)
は偶数番目の要素を3倍にするし、sumOf elementsEven
は偶数番目の要素の合計を求める関数になる。
Lens
Traversal
が複数の要素を扱うのに対し、Lens
と呼ばれる構造はただ1つの対象しか扱わない。
type Lens s t a b = forall f. Functor f => (a -> f b) -> s -> f t
Lens
はTraversal
でもあり、Setter
かつGetter
である。レコードのフィールドの表現に使えるだけあって人気も高く、パッケージ名になっているほどである。
Lens
はtraverse
と似たような感覚で作れる。Traversalは0個や複数の要素を扱うのにpure
や<*>
を使うが、要素が1つならばfmap
だけでよいというのはわかりやすい。
Review
ReviewはGetterを逆転させたものだ。回りくどいが、Review t b
はb -> t
という関数と等価である。
type AReview t b = Tagged b (Identity b) -> Tagged t (Identity t)
(#) :: AReview t b -> b -> t
(#) l = runIdentity . unTagged . l . Tagged . Identity
Prism
type Prism s t a b = forall p f. (Choice p, Applicative f) => p a (f b) -> p s (f t)
Prism
はTraversal
かつReview
である。これは作りにくいので、自分で定義する際はprism :: (b -> t) -> (s -> Either t a) -> Prism s t a b
という関数を使おう。
_Just :: Prism (Maybe a) (Maybe b) a b
_Just
はMaybe
に対するtraverse
としての機能を持つが、Review
でもあるため、_Just # 42
からJust 42
を得るということができる。代数的データ型のコンストラクタと似たような雰囲気で、値を埋め込んだり取り出したりできる。
Iso
type Iso s t a b = forall p f. (Profunctor p, Functor f) => p a (f b) -> p s (f t)
Iso
はLens
かつPrism
であり、こちらは要素が必ず1つあることが保証されている。したがって同型な二つのデータ型にならIso
を定義できる。こちらはヘルパー関数なしでも作りやすい。
enum :: Enum a => Iso Int Int a a
enum = dimap toEnum (fmap fromEnum)
Equality
type Equality s t a b = forall p f. p a (f b) -> p s (f t)
Equality
はIso
よりさらに強い。Equality
になる値はid
ただ一つしかないため、型の等価性を表現する。等価性を採取して他の場所に運びたいときに使えるが、その目的ならbase
パッケージのData.Type.Equality
があるため、使う場面はかなり少ない。
まとめ
構造を「使いたい」と思ったときは弱いほうから、「作りたい」と思ったら強いほうから、対応するモジュールを調べてみよう。この力の関係を知っていれば、よりよくlensを活用できるに違いない。