[Elm]SelectListの つ か い か た

最近、[Bibliopola(Storybook)](https://qiita.com/miyamo_madoka/items/1363f2b34bc8e650a791)とか[elm-local-packages](https://github.com/miyamoen/elm-local-packages)といった開発ツールを作ってます

その前段として[SelectList](https://package.elm-lang.org/packages/miyamoen/select-list/latest)というパッケージを作って、使ってます
これが結構便利でよく使ってるので使い方をステップバイステップで書いていこうと思います

[SelectListを使ってくれてた人に聞いたらselectedMapForListをお願いされたのでそれやります](https://twitter.com/enma/status/1069835609294786560)

## SelectListってなに

そもそもSelectListって何かっていうとListのうち１つの要素を選択した状態を表現する型です

`[ 1, 2, 3, 4 ]`こういうリストがあって
`[ 1, 2, "3", 4 ]`3を選択している
というのをElmで表現する場合どうするか

```elm
type SelectList a =
    SelectList (List a) a (List a)
```
こういう風になります

真ん中の`a`が選択している値ですね

以降では`[ 1, 2, "3", 4 ]`のように選択している要素を`""`で囲んで表現したいと思います

## どういうときに使いたくなるのか

UI作っていると複数要素があるうちの１つを選択しているってことはそれなりにあります
タブとか、セレクトボックスとか。タブは複数あって、そのうちの1つが表示されているのでSelectListで表現できます。セレクトボックスも複数の選択肢のうち1つを選んでいるのでSelectListで表現できます。


さて、選択状態があるリストを表現するのに使えるよってのがSelectListなのですが、実はただのListでもSelectListの恩恵を得られます

それが`selectedMapForList`です

# 型

何はともあれ型を見ましょう。ドキュメントなんて飾りです。型 ｳｿﾂｶﾅｲ

```elm
selectedMapForList : (SelectList a -> b) -> List a -> List b
map                : (           a -> b) -> List a -> List b
```

ついでに`List.map`の型も並べてみました
こうして並べてみると一目瞭然ですね。selectedMapForListはList.mapと同じようにListに対して使えそうです

## 個別の要素に関数を適用したいんだけど…

例えば`view`関数を要素に適用したいとするとこうなるはずです

```elm
List.map view [ 1, 2, 3, 4 ]
== [ view 1, view 2, view 3, view 4 ]
```
決してSelect __List__ を引数に取りたいわけではないですよね。selectedMapForListの引数の関数はSelect __List__ を引数に取る関数なのでなんか変です

こんな感じになります

```elm
selectedMapForList view [ 1, 2, 3, 4 ]
== [ view [ "1", 2, 3, 4 ]
   , view [ 1, "2", 3, 4 ]
   , view [ 1, 2, "3", 4 ]
   , view [ 1, 2, 3, "4" ]
   ]
```

選択した要素をずらしながら渡します。`SelectList.selected`を使えば選択状態の要素を取り出せるので、selectedMapForListがList.mapの上位互換だということが分かると思います

## List.indexedMapと比較

まず型です。

```elm
selectedMapForList : (SelectList a -> b) -> List a -> List b
indexedMap         : (    Int -> a -> b) -> List a -> List b
```

Intが増えてますね。要素のindexが渡されてます

実はindexもSelectListから取れちゃいます。[SelectList.index](https://package.elm-lang.org/packages/miyamoen/select-list/latest/SelectList#index)です

つまりindexedMapの上位互換であるということも分かりましたね


## 実際に使っているところ見てみよう

[exmaples](https://github.com/miyamoen/select-list/tree/4.0.1/example)のコードを使います

[複数のカウンターがあるアプリです。](https://github.com/miyamoen/select-list/blob/4.0.1/example/src/ListWithSelectList.elm)
機能を絞っているので数字をクリックすると増える動作だけ実装されています

```elm
type alias Model =
    { nums : List Int
    }
```
モデルです。Listですね

```elm
type Msg
    = ClickNumber (SelectList Int)
```

Msgです。諸事情によりSelectListがわたってきていますがListでもいいです（後でまた言及します）

```elm
view : Model -> Html Msg
view model =
    div [] <| SelectList.selectedMapForList renderRow model.nums


renderRow : SelectList Int -> Html Msg
renderRow selected =
    div
        [ Events.onClick (ClickNumber selected) ]
        [ text <| String.fromInt <| SelectList.selected selected ]
```

viewです。renderRowは`SelectList Int`を取ります。
selectedで取り出して表示していますね。onClickではSelectListをそのままMsgに包んでupdateに送っています。ということでupdateを見てみましょう

```elm
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        ClickNumber selectList ->
            ( { model
                | nums =
                    SelectList.toList <|
                        SelectList.updateSelected ((+) 1) selectList
              }
            , Cmd.none
            )
```

やってきた`selectList`を`updateSelected`で選択している要素を1増やして`toList`でListに戻した後numsを更新しています。
クリックされた数字が`selectList`で選択されているのでこれでちゃんと更新されます。

これはupdate内に処理を書いたんですがちょっとview内に書くように変えてみましょう

```elm
type Msg
    = SetNums (List Int)

renderRow : SelectList Int -> Html Msg
renderRow selected =
    div
        [ Events.onClick (SelectList.updateSelected ((+) 1) selected |> SelectList.toList |> SetNums) ]
        [ text <| String.fromInt <| SelectList.selected selected ]
```

Msgを更新後のnumsを受け取るように変更しましょう。updateは更新するだけで自明なので略です。
renderRowでSelectListを使って更新後のListを作っています。

このように、個別要素を表示するview内から直接全体を更新する処理を書けます

indexedMapを使う場合はindexを受け取るので、これを使ってupdateにどのindexの要素がクリックされたか教える感じになりますElmではindexベースの更新処理はちょっと煩雑なのでそれが解消されるだけでも結構便利ですね

## 更なる力を・・・

実はSelectListには更なる力がありますが、今回はもう終わりです。軽く紹介だけします
SelectListの更なる力とは隣の要素が見えたり、隣と入れ替えたりすることです
[こっちのexample](https://github.com/miyamoen/select-list/blob/4.0.1/example/src/UpDownWithSelectList.elm)でその力を使っているので興味があればみてみてください

## 終わり

フィードバック歓迎です