Lensことはじめ
見ろ! Haskell が OOPL のようだ!
さてさて、ようやく重い腰を上げて、Lens を勉強し始めましたよ。Haksell for allを見て勉強すればいいのかなと思ったんですが、解説しているパッケージが data-lens なので古いですね。
今、使うべきなのは、lens というパッケージらしいです。解説は、この README を読むのが一番だそうです。この README と Haskell for all をにらめっこしながら、Lens の getter と setter の機能を使ってみます。
背景
Haskell の代数データ型にはフィールドラベルが定義できて、これがいわゆる getter と setter の役割を果たします。Haskell for all から例を引用してみましょう。
data Point = Point { x :: Double , y :: Double } deriving Show
この定義を GHCi で読み込んで、適当な値を作って p という名前を与えます。
> let p = Point 3.0 4.0
フィールドラベルは getter 関数として使えます。
> x p 3.0
また、フィールドラベルを使えば、差分を指定することで新しい値を作り出せます。
> p { x = 5.0 } Point {x = 5.0, y = 4.0}
さて問題は、ここからです。次に Point を格納する Circle を定義してみましょう。
data Circle = Circle { center :: Point , radius :: Double } deriving Show
この Circle を右に動かす関数を書くとこうなります。
goRight c d = c { center = (center c) { x = x (center c) + d } }
きゃー。醜いですね。せっかくなので使ってみましょう。
> let c = Circle p 5.0 > c Circle {center = Point {x = 3.0, y = 4.0}, radius = 5.0} > goRight c 6.0 Circle {center = Point {x = 9.0, y = 4.0}, radius = 5.0}
Lensを使う
Lens を使う場合、フィールドラベルを "_" ではじめて makeLenses という呪文を唱えればいいようです。
data Point = Point { _x :: Double , _y :: Double } deriving Show data Circle = Circle { _center :: Point , _radius :: Double } deriving Show makeLenses ''Point makeLenses ''Circle
Getter は "^." で利用できます。
> let p = Point 3.0 4.0 > p^.x 3.0
Getter のチェーンも使えます。
> let c = Circle p 5.0 > c^.center^.x 3.0
Setter は ".~" です。"&" と一緒に使います。
> p&x.~5.0 Point {_x = 5.0, _y = 4.0}
数値であれば、"+~"、"-~"、および "*~" が使えます。
> p&x+~6.0 Point {_x = 9.0, _y = 4.0}
Setter のチェーンを実現するには丸括弧が必要なようです。
> c&(center.x).~7.0 c&(center.x).~7.0 Circle {_center = Point {_x = 7.0, _y = 4.0}, _radius = 5.0} >c¢er&x.~7.0 これはエラー
という訳で、goRight は以下のように実装できます。
goRight c v = c&(center.x)+~v
使ってみましょう。
> goRight c 6.0 Circle {_center = Point {_x = 9.0, _y = 4.0}, _radius = 5.0}
めでたし。めでたし。
どんなに恐ろしい構文を持っても
たくさんのかわいそうな定型コードを操っても
λから離れては生きられないのよ