あどけない話

インターネットに関する技術的な話など

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&center&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}

めでたし。めでたし。

どんなに恐ろしい構文を持っても
たくさんのかわいそうな定型コードを操っても
λから離れては生きられないのよ