お問い合わせ

技術

関数型言語でオブジェクトを扱うためのLens型

2024.02.21

著者:井上 健太

関数型言語でオブジェクトを扱うための型であるLensについて、関数型言語Haskellを用いて解説します。

まず、オブジェクト指向の特徴であるgetterとsetterを型を使って表してみます。

getterとsetterの型

オブジェクトの型をsとし、そこに格納する変数の型をaとした時、getter関数get、setter関数setはそれぞれ以下の型になります。

get :: s -> a set :: s -> a -> s

ただし、この2つの関数をオブジェクトとみなすためには、任意のオブジェクトsと入力abに対し、以下の性質を満たす必要があります。

get (set s a) = a set s (get s) = s set (set s b) a = set s a

1つ目はオブジェクトに値asetした直後にgetすると同じ値が得られること、 2つ目はgetした値をそのままsetしたものは元のオブジェクトと等しいこと、 3つ目はsetを複数回繰り返しすことは、最後のsetだけ適用するのと変わらないことを表しています。

getter、setterからLensを作る

オブジェクトを扱う関数としてはsetgetがわかりやすいですが、この2つを合わせた以下のようなMyLens型の関数が作れます。

type MyLens s a = forall f. Functor f => (a -> f a) -> s -> f s

実際にgetsetからこの型の関数が定義でき、その逆も定義できます。

{- get & set -> lens -} mkMyLens :: (s -> a) -> (s -> a -> s) -> MyLens s a mkMyLens get set f s = set s <$> f (get s) {- lens -> get -} mkGet :: MyLens s a -> s -> a mkGet lens = getConst . lens Const {- lens -> set -} mkSet :: MyLens s a -> s -> a -> s mkSet lens s a = runIdentity $ lens (\_ -> Identity a) s

さらに、この定義から任意のgetsetに対し、以下の性質を満たすことも分かります。

mkGet (mkLens get set) s = get s mkSet (mkLens get set) s a = set s a

つまり、getsetの2つをMyLens型1つで表現できるということです。 しかしながら、Haskellの実際のLensの型はこのMyLensとは異なります。



実際のLens

ライブラリ上では、上で定義したMyLensではなく、これを一般化したLensという型を使います。

type Lens s t a b = forall f. Functor f => (a -> f b) -> s -> f t

先ほどのMyLensと比較すると、型引数がより一般化したものになっていて、実際にMyLensLensを使って記述できます。

type MyLens s a = Lens s s a a

実はMyLensLens'という名前で既に実装されています。

type Lens' s a = Lens s s a a

LensとGetter、Setterとの関係

このLensに対応するGetter、Setterの型はそれぞれ

Lens s t a b :: forall f. Functor f => (a -> f b) -> s -> f t get :: s -> a set :: s -> b -> t

になっており、上の例のようにこのgetsetからLens型の関数を作れ、逆にLens型の関数からgetsetを作れます。

{- get & set -> Lens -} lens :: (s -> a) -> (s -> b -> t) -> Lens s t a b lens sa sbt afb s = sbt s <$> afb (sa s) {- Lens -> get -} myView :: MonadReader s m => Lens s t a b -> m a myView lens = Control.Monad.Reader.asks (getConst . lens Const) myView' :: Lens s t a b -> s -> a myView' = myView {- view :: MonadReader s m => Getting a s a -> m a -} {- type Getting r s a = (a -> Const r a) -> s -> Const r s -} {- Lens -> set -} set :: ASetter s t a b -> b -> s -> t set l b = runIdentity . l (\_ -> Identity b) mySet :: Lens s t a b -> s -> b -> t mySet lens = flip $ set lens {- type ASetter s t a b = (a -> Identity b) -> s -> Identity t -}

viewの方はライブラリではLens s s a a上のみで定義されていますが、上のmyViewのように、一般化してLens s t a b上で定義できます。

また、MyLensの時と同様に任意のgetsetに対し、以下の性質が成り立ちます。

myGet (lens get set) s = get s mySet (lens get set) s a = set s a


まとめ

関数型言語でオブジェクトを表すのにLens型があればいいことがわかりました。


一覧に戻る


採用情報

Recruit


“技術で世界に勝負をかけて未来を拓く”
当社はこのような志で設立され、業界のリーディングカンパニーを目指して努力を続けています。
若い会社であるため、入社した社員全員で歴史を作り上げていくことを実感できることでしょう。
現在、採用活動を積極的に展開中です。
技術で世界に勝負をかける日本インサイトテクノロジーで、存分にお力を発揮してください。


新卒採用情報はこちら
キャリア採用情報はこちら


お問い合わせ

Contact


お問い合わせはこちら