elm-ui の table に渡す Column の view を関数合成で書く

elm-ui の `table` のサンプルコードだと `Column` の `view` を匿名関数で書いてるけど、関数合成使えばすっきり書けるじゃね？という記事です。


## `table` の定義とサンプルコード

elm-ui の [Element モジュールのドキュメント](https://package.elm-lang.org/packages/mdgriffith/elm-ui/latest/Element) から引用します。

[`table`](https://package.elm-lang.org/packages/mdgriffith/elm-ui/latest/Element#table) の定義：

```elm:Element.table
table :
    List (Attribute msg)
    -> { data : List records
       , columns : List (Column records msg)
       }
    -> Element msg
```

`table` の定義に出てくる [`Column`](https://package.elm-lang.org/packages/mdgriffith/elm-ui/latest/Element#Column) 型の定義：

```elm:Element.Column
type alias Column record msg =
    { header : Element msg
    , width : Length
    , view : record -> Element msg
    }
```

`table` のサンプルコード：

```elm:データ
type alias Person =
    { firstName : String
    , lastName : String
    }

persons : List Person
persons =
    [ { firstName = "David"
      , lastName = "Bowie"
      }
    , { firstName = "Florence"
      , lastName = "Welch"
      }
    ]
```

```elm:テーブル
Element.table []
    { data = persons
    , columns =
        [ { header = Element.text "First Name"
          , width = fill
          , view =
                \person ->
                    Element.text person.firstName
          }
        , { header = Element.text "Last Name"
          , width = fill
          , view =
                \person ->
                    Element.text person.lastName
          }
        ]
    }
```

`table` の 2個目の引数の `data` フィールドに大元のデータを指定して、`columns` フィールドに列ごとの定義を記述する感じです。

ちなみにサンプルコードは `fill` が `Element.` で修飾されてないのでコンパイルエラーになります。
試す場合は [`Element.fill` に修正しましょう](https://ellie-app.com/62XN9ykPTTpa1) 。


## `Column` の `view` をすっきりさせる

```elm:Columnのview
view =
    \person ->
        Element.text person.firstName
```


### 匿名関数をなくしたい

上記の `view = \person -> ...` をいい感じにしたいです。
匿名関数での記述はちょっといい感じじゃないですよね [^1] 。インデントが深くなるとかね。

[^1]: サンプルコード自体は「関数をどう使えばいいか」を説明するためのものなので、匿名関数を使った書き方で正解。使い方が分かってくるともうちょっといい感じに書きたくなるので、ここではその方法を述べる。

例えば `Column record msg` の `record` が `String` だったら、`view = \person -> ...` じゃなくて `view = Element.text` と書いて完了です (`view` の型が `String -> Element msg` になり、`Element.text` の型と一致するので) 。
こんな感じに書けると嬉しいです。

しかしサンプルコードでは `Person` 型です。
型の実体としては `{ firstName : String, lastName : String }` 。
`Person` 型を引数に指定したら `firstName` フィールドや `lastName` フィールドの値を返す関数があれば問題解決しそうなのに、って思いますよね。

ありますね。


### フィールドアクセス関数

Elm では `.フィールド名` は `{ b | フィールド名 : a } -> a` という型の関数です [^2] 。
任意のレコード型 `b` から `フィールド名` で指定した `a` 型のフィールドの値を返す関数、ともいえます。
サンプルコードの場合は、`Person` 型の値に `.firstName` を適用すると `firstName` フィールドの `String` 型の値を取得することができます [^3] 。

[^2]: [Elm Syntax によるとフィールドアクセス関数 (field access function) と呼ぶとか呼ばないとか](https://elm-lang.org/docs/syntax#records) 。ページ内のサンプルコードのコメントにあるだけなので正式な名称ではないのかも。

[^3]: `Person` 型に限らず `firstName` フィールドを持っている任意のレコード型に適用できる。


### 関数合成

`.firstName` で `Person` 型から `String` 型のフィールド値を取り出せることが分かりました。
`.firstName` の戻り値と `Element.text` の引数が同じ `String` 型なので関数合成の出番です。
こうなります：

```elm:関数合成でviewを定義
Element.table []
    { data = persons
    , columns =
        [ { header = Element.text "First Name"
          , width = Element.fill
          -- view : { Person | firstName : String } -> Element msg
          , view = Element.text << .firstName
          }
        , { header = Element.text "Last Name"
          , width = Element.fill
          -- view : { Person | lastName : String } -> Element msg
          , view = Element.text << .lastName
          }
        ]
    }
```

できましたね。

[実際に動くモノ＠Ellie](https://ellie-app.com/63jkLrjPyTha1)


## まとめ

やったことは関数合成を使って匿名関数を書き直しただけです。
ただ、elm-ui の `table` では入力元データは大抵がレコード型 (のリスト) のはずなので、`view` をフィールドアクセス関数と関数合成で表現できることを覚えておくと有用な場面が多いと思います。


**おしまり**