yunomuのブログ

趣味のこと

lens

しーんじてーこーころがとーきーめーいたー瞬間をー

遠い昔のことですが、こんな記事を書きました。
record update - yunomuのブログ
recordのupdateめんどくさいよねという話。

で、コメント欄でふみさんに「lensあるよ」って言われました。
http://hackage.haskell.org/package/lens
あるよっていうか、存在は知っていたけど、こういう用途に使うものだったんですね。デカい図が貼ってあるけど何言ってるかわかんねぇ。

とりあえず使ってみました。

{-# LANGUAGE TemplateHaskell #-}
module Main where

import Control.Lens
import Data.Text (Text)

data Music = Music
    { _title :: Text
    , _count :: Int
    }
  deriving (Show)
makeLenses ''Music

とりあえずこれで準備OK。lens使う時はレコードのフィールド名を'_'で始めないといけないっぽいです。
あとはmakeLensesのためのTemplateHaskell拡張。このとき、例えばこのMusicというデータの中で他のデータ型を参照している場合はこの部分より前でそのデータ型を定義しておく必要があるとか、titleとかcountとかいう関数が自動生成されるから注意するとか、TemplateHaskell的な制約が少々。
あとアンダースコアがそもそもダサいとか。その辺は、lensを使うとそもそも生のフィールド名を使わずに済みそうなので、割り切って見なかった方向で行くといいのかもしれない。

% ghci -XOverloadedStrings
...
[1 of 1] Compiling Main             ( Main.hs, interpreted )
Ok, modules loaded: Main.
*Main> let music = Music "純愛レンズ" 0
*Main> music
Music {_title = "\32020\24859\12524\12531\12474", _count = 0}

例題が微妙にマズかった。なんでTextで日本語にしたんだ。それはそうと、これだとレコードの更新はこんな風に書ける。

*Main> let a = Music "Mermaid festa vol.1" 0
*Main> a
Music {_title = "Mermaid festa vol.1", _count = 0}
*Main> a{_count = _count a + 1}
Music {_title = "Mermaid festa vol.1", _count = 1}
*Main> a&count %~ (+1)
Music {_title = "Mermaid festa vol.1", _count = 1}

従来と比較して、まあ正直どっちがいいんだかはよくわからないけども、少なくとも`a`とか`_count`が2回出てこないのは良い。

でもこの書き方のいいところは他のデータ構造と一緒に使った時で
例えばMapでadjustを使うとき

*Main> import Data.Map
*Main Data.Map> let m = insert 9 a empty
*Main Data.Map> m
fromList [(9,Music {_title = "Mermaid festa vol.1", _count = 0})]
*Main Data.Map> adjust (\v -> v{_count = _count v + 1}) 9 m
fromList [(9,Music {_title = "Mermaid festa vol.1", _count = 1})]
*Main Data.Map> adjust (&count %~ (+1)) 9 m
fromList [(9,Music {_title = "Mermaid festa vol.1", _count = 1})]

下の2つは同じ事をしているんだけど、要らないところが削れてだいぶ良い感じになったような気がする。構文というか、演算子が見やすいかどうかは意見が別れるところだろうけども。

という風に、Data.Map.adjustとかControl.Monad.State.modifyとかを多用する私みたいな人には割とおすすめなライブラリかもしれないです。
普通にgetterとかsetterっぽいのもあります。

*Main> a^.count
0
*Main> a^.title
"Mermaid festa vol.1"
*Main> a&title .~ "No brand girls"
Music {_title = "No brand girls", _count = 0}
*Main> a&title .~ "Love marginal" &count .~ 3
Music {_title = "Love marginal", _count = 3}

というか単に私がまだこの3つの操作しか覚えてないだけですが、もっといろんな使い方を知ると便利なライブラリになると思います。たぶん。

こんなかんじで更新を抽象化してる風な関数を定義したりとかもできますけど、

*Main> let upd rec field value = rec&field .~ value
*Main> upd a title "sweet & sweet memories"
Music {_title = "sweet & sweet memories", _count = 0}

[(FieldName, Value)]みたいな組のリストを渡して突っ込むみたいな事は、組のリストを作れない関係でできないので、作ってもあんまし意味が無い気がしますね。

でもこの3つの操作を覚えるだけでも結構気持ちよくやれる気がします。recordなんて要らんかったんや!

ソース
https://github.com/yunomu/exercises/blob/master/lens/Main.hs