elm-monocleでスッキリする

# elm-monocle

## elm-monocle とは
- http://package.elm-lang.org/packages/arturopala/elm-monocle/latest
- 僕は http://qiita.com/miyamo_madoka/items/f225803d275019cc34c1 を読んで知りました
- scalaのMonocleのインスパイアライブラリ
  - [scala Monocle](http://julien-truffaut.github.io/Monocle/)
   - scala MonocleはHaskellのLensからインスパイア
- 複雑なレコードを操作するためのライブラリ


## どんなときに便利なのか
- 複雑なレコードを操作するとき

### 映画は物語を持つというレコードがあるとして

```elm

type alias Story =
    { text : String
    }


type alias Movie =
    { name : String
    , story : Story
    }
```
### スターウォーズシリーズの外伝が作られることになりました
```elm
starwarsSpinOff : Movie
starwarsSpinOff =
    Movie "starwars:SpinOff" (Story "遠い...未定")
```
### 正式タイトルが決まり、ストーリーも更新されます！
```elm
rogueOne : Movie
rogueOne =
    { starwarsSpineOff
        | name = "rogueOne: A star Wars Story"
        , story = { starwarsSpineOff.story | text = "遠い昔、はるか彼方の銀河系で…。" }
    }
```
### しかし、これではコンパイルが通りません。 なので以下のようにしました
```elm
rogueOne : Movie
rogueOne =
    let
        oldStory =
            starwarsSpineOff.story

        newStory =
            { oldStory | text = "遠い昔、はるか彼方の銀河系で…。" }
    in
        { starwarsSpinOff
            | name = "rogueOne: A star Wars Story"
            , story = newStory }
        }
```
レコード内の奥深くのフィールドを更新して新しいレコードを作ろうとすると見辛いコードが増えていきますね‥


## こんなとき elm-monocleのLensを使うと見栄えがよくなる
```elm
import Monocle.Lens exposing (Lens, compose)
~~略~~

textOfStory : Lens Story String
textOfStory =
    let
        get story =
            story.text

        set text story =
            { story | text = text }
    in
        Lens get set


type alias Movie =
    { name : String
    , story : Story
    }

nameOfMovie : Lens Movie String
nameOfMovie =
    let
        get movie =
            movie.name

        set name movie =
            { movie | name = name }
    in
        Lens get set

storyOfMovie : Lens Movie Story
storyOfMovie =
    let
        get movie =
            movie.story

        set story movie =
            { movie | story = story }
    in
        Lens get set


rogueOne : Movie
rogueOne =
    starwarsSpinOff
        |> nameOfMovie.set "rogueOne: A star Wars Story"
        >> (compose storyOfMovie textOfStory).set "遠い昔、はるか彼方の銀河系で…。"
```

hogeOfHuga: Lens Huge Huga
という関数が増えていてコード自体の量は増えていますが
「rogueOne」を作るコードはすっきりしていませんか？

hogeOfHuga: Lens Huge Huga のコードも単純です
レコードのフィールド一つに対して定義されていて
レコードからフィールドの値をとる`get`と
レコードのフィールドを更新して新しいレコードを返す`set`を定義しているだけで非常にシンプルです

`starwarsSpinOff |> nameOfMovie.set "rogueOne: A star Wars Story"`は`starwarsSpinOff`の`name`フィールドに`"rogueOne: A star Wars Story"`をセットして新しいMovieを返しています

`(compose storyOfMovie textOfStory)`にある`compose`は名の通り合成で、`Movie`をとって`story`フィールドの、Storyの`text`フィールドに対する`Lens`を定義(getとset)してます
なのでここでは`Movie`の`story`フィールドの`Story`の`text`フィールドに文字列をセットして新しいレコードとして返しています

## monocleのOptionalを使うともっと楽しくなる
Movieレコードに変更します
storyをMaybe Storyとしましょう
映画には物語がないようなものもあるでしょう

```elm
type alias Movie =
    { name : String
    , story : Maybe Story
    }
```

`Story`レコードに変更をします
`Story`には`goodPoint`というフィールドがあるとしましょう
映画には見所があるものとないものがあるので`Maybe`で定義しましょう

```elm
type alias Story =
    { text : String
    , goodPoint : Maybe GoodPoint
    }
```

映画の見どころとは言葉で表せないこともあるのでフィールド`text`はMaybeです

```elm
type alias Text = String
type alias GoodPoint =
    { text : Maybe Text
    }
```

ここでmonocleなしで`Movie`から`goodPoint`のフィールド`text`取ってこようとすると...

```elm
rogueOne = Movie "rogueOne: A star Wars Story" (Just <| Story "遠い..." (Just <| GoodPoint (Just "イケてる俳優")))

rogueOneGoodPoint : Maybe Text
rogueOneGoodPoint =
    rogueOne.story
        |> Maybe.map
            (\{ goodPoint } ->
                goodPoint
                    |> Maybe.map (\{ text } -> text)
                    |> Maybe.withDefault Nothing
            )
        |> Maybe.withDefault Nothing

```

`map`の連打、`withDefault`の嵐、`Maybe`が増えるほどコードがネストすることを察せます

これがset系の操作になったら

```elm
updatedRogueOne : Movie
updatedRogueOne =
    let
        newGoodPoint : Maybe GoodPoint
        newGoodPoint =
            rogueOne.story
                |> Maybe.map
                    (\story ->
                        story.goodPoint
                            |> Maybe.map (\goodPoint -> { goodPoint | text = "かっこいい宇宙戦" })
                    )
                |> Maybe.withDefault Nothing

        newStory : Maybe Story
        newStory =
            rogueOne.story |> Maybe.map (\story -> { story | goodPoint = newGoodPoint })
    in
        { rogueOne | story = newStory }
```
僕のウデマエでは非常に大変なコードになってしまいました

### Optionalを使ってみよう
```elm
import Monocle.Optional exposing (Optional)
import Monocle.Common exposing ((=>))
~~略~~

textOfGoodPoint : Optional GoodPoint Text
textOfGoodPoint =
    let
        get goodPoint =
            goodPoint.text

        set text goodPoint =
            { goodPoint | text = Just text }
    in
        Optional get set

goodPointOfStory : Optional Story GoodPoint
goodPointOfStory =
    let
        get story =
            story.goodPoint

        set goodPoint story =
            { story | goodPoint = Just goodPoint }
    in
        Optional get set

--- Lensの例から定義を変更してます
storyOfMovie : Optional Movie Story
storyOfMovie =
    let
        get movie =
            movie.story

        set story movie =
            { movie | story = Just story }
    in
        Optional get set

rogueOneGoodPoint : Maybe Text
rogueOneGoodPoint =
    rogueOne |> (storyOfMovie => goodPointOfStory => textOfGoodPoint).getOption

updatedRogueOne : Movie
updatedRogueOne =
    rogueOne |> (storyOfMovie => goodPointOfStory => textOfGoodPoint).set "かっこいい宇宙戦"

```

Lensのときのように`hogeOfHuga`のような`Optional`の定義が`Maybe`なフィールドの数だけあります
とはいってもこれらの定義もシンプルですね

```elm
--- MovieからGoodPointのTextをとってくる
rogueOneGoodPoint : Maybe Text
rogueOneGoodPoint =
    rogueOne |> (storyOfMovie => goodPointOfStory => textOfGoodPoint).getOption

--- MovieのGoodPointのTextを更新して新しいMovieを返す
updatedRogueOne : Movie
updatedRogueOne =
    rogueOne |> (storyOfMovie => goodPointOfStory => textOfGoodPoint).set "かっこいい宇宙戦"

```

`=>`はOptional同士のcomposeをする中置関数です

こうするとよりスッキリ

```elm
movieStoryGoodPointText =
    storyOfMovie => goodPointOfStory => textOfGoodPoint

rogueOneGoodPoint : Maybe Text
rogueOneGoodPoint =
    rogueOne |> movieStoryGoodPointText.getOption

updatedRogueOne : Movie
updatedRogueOne =
    rogueOne |> movieStoryGoodPointText.set "かっこいい宇宙戦"

```

`Movie`の`Story`の`GoodPoint`の`Text`を取り出すコードや、

`Movie`の`Story`の`GoodPoint`の`Text`を更新して新しい`Movie`を返すコード

非常にスッキリですよね


`Maybe`のための`withDefault`や`map`がコードから綺麗さっぱりいなくなっていますね
合成された`Optional`は`get`や`set`するためにフィールドを辿るどこかで`Nothing`があったとき、`get`に対して`Nothing`を返したり`set`をしないということをmonocleがヨロシクしてくれます


## LensとOptionalが混ざっているときには

映画のいいところは絶対に言葉になるはずだ！と強い気持ちでフィールド`text`は`Maybe`でなくなりました

```elm
type alias GoodPoint =
    { text : Text
    }

rogueOne = Movie "rogueOne: A star Wars Story" (Just <| Story "遠い..." (Just <| GoodPoint "イケてる俳優"))

```

そうすると`textOfGoodPoint`の定義は`Optional`から`Lens`になります

```elm
textOfGoodPoint : Lens GoodPoint Text
textOfGoodPoint =
    let
        get goodPoint =
            goodPoint.text

        set text goodPoint =
            { goodPoint | text = text }
    in
        Lens get set
```

そして実際に`Movie`から`GoodPoint`の`Text`を取り出すコードと

`Movie`の`GoodPoint`の`Text`を更新して新しい`Movie`を返すコードは以下になります

```elm
movieStoryGoodPointText =
    storyOfMovie => (composeLens goodPointOfStory textOfGoodPoint)

rogueOneGoodPoint : Maybe Text
rogueOneGoodPoint =
    rogueOne |> movieStoryGoodPointText.getOption

updatedRogueOne : Movie
updatedRogueOne =
    rogueOne |> movieStoryGoodPointText.set "かっこいい宇宙戦"
```

OptionalとLensを合成するために`composeLens`を利用するようにしました
MaybeなフィールドとMaybeでないフィールドも合成できました！

## まとめる
- monocleはネストしたレコードの操作をスッキリ書ける
- LensとOptionalはレコードのある１つのフィールドのgetとsetをセットで渡して定義する
- つかうのは Lens , Optional , compose, composeLens, =>
    - MaybeはOptional, それ以外はLens
- LensとOptionalは合成してレコード操作していく


## 最後に
- Iso, Prismなどmonocleにはまだいくつもできることがある
- ローグワンおもしろかったです