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らしく書けるようにしたものです。
シンプルなコードではあまり使うことなさそうですが、
一つの関数で何度もフィールドラベルを使用する時には役に立ちそうですね