wvogel日記

自分用の技術備忘録が多めです.

Functional Lensとは?

友人曰く、functional lensなるものがあるらしい、調べよ!
との事で、調べてみた。

Lenses←ここの記事が参考になりました。
読んだところ、C#でいうところの、get,setと同様の機能をより関数型に実装したものがLensのようです。

例えば、あるデータを一まとめにしたタプルがあるとします。
その場合、getやsetは次のように書けます。

--get

--tuple
getC (_,_,c,_,_,_) = c
--data
getX d = x d

--set
--tuple
setB b' (a,b,c,d,e,f) = (a,b',c,d,e,d)
--data
setX x' d = d {x = x'}

これは私も何度か使用した書き方です。
タプルの場合では、タプルのもつ要素数が増えるに従いあほらしくなります。それを回避するために導入されたのが代数的データ型であり、見ての通りとてもシンプルに値のget,setが実現されます。
しかし、フィールドラベルは第一級関数ではありません。
そのため、ポイントフリースタイルなどの関数合成にフィールドラベルを使えないのです!

ここらへんの問題を解決するためにあるのがLensです。
これは、getter,setterとなる二つの関数からなります。

type Lens a b = ( a -> b , b -> a -> a )

Lensを作るにはData.Lens.Lazyに用意されているlens関数を使います。

lens :: (a -> b) -> (b -> a -> a) -> Lens a b

例として、車のデータを保持するためのCar型を作りました。
全容はココに置きました。

車は、始点からの走行距離、速度そして燃料を保持しているものとします。位置、すなわち走行距離に関してのget,setは次のようにしました。

getPos :: Car -> Int
getPos = pos
setPos :: Int -> Car -> Car
setPos x car = car {pos = pos car + x}

そして、これをStateモナドと合わせて使用した例がココです。
(余談ですが、初めて自分で書いたモナド変換子利用コード)

また、Lens型のset,get関数であるsetL,getLは、(^=),(^.)で書けます。
どちらも(^)を消して考えるとわかりやすい。

コードの内容は適当です。
注目すべきはupdateCar関数で、次のようになります

updateCar :: Int -> Int -> Int -> Car -> Car
updateCar v p f = (velocityL^=v).(posL^=p).(fuelL^=f)

ポイントフリースタイルが使えるーー!!

と、このように、手続き型でのget,setをより関数的に、Haskellらしく書けるようにしたものです。

シンプルなコードではあまり使うことなさそうですが、
一つの関数で何度もフィールドラベルを使用する時には役に立ちそうですね