arowM/elm-form-decoderのAPIを【かんぜんりかい】しよう！

## version

- elm 0.19.0
- elm-form-decoder 1.2.0

## はじめに

最近Elmではやりのelm-form-decoderのAPIを1個ずつ見ていきます
(お仕事コードでだいぶ使ってしまったので説明用です)

elm-form-decoderの背景やお気持ちについては作者の[「フォームバリデーションからフォームデコーディングの時代へ」](https://qiita.com/arowM/items/6c32db1f9e4b92445f3b)の記事をご覧ください

上記記事をみていただければいいんですが軽く説明するとフォームデコーダーはいわゆるバリデーションライブラリの発展形です。**お前はユーザーの入力した値をチェックするだけでいいのか？……違うだろ！欲しいデータ型に変換もするだろ！！**って感じです

「入力をバリデーションしつつ変換する」がやりたいことです
ということでいちからどんなモジュールか調べていきましょう

## 型

知らないライブラリを調べるときはまず型をみましょう

```elm
type Decoder input err a
    = Decoder (input -> Result (List err) a)
```

入力を受け取って`Result (List err) a`を返す関数か……もう完全にわかりましたね。解散です！🐐

デコーダーはある型からある型への変換を表す型なので、左の`input`が入力の型、真ん中の`error`が変換中にエラーになったときのエラーの型、右の`a`が変換後の型になります
左から右に流れる感じにみえますね

## `Decoder`を使う関数

型をみたら次はその型を使って一般的な型に変換する関数を探しましょう。モジュール外との境界面ですね
今回はデコーダーなのでデコードするときに使う関数です

```elm
run    : Decoder input err a -> input -> Result (List err) a
errors : Decoder input err a -> input ->         List err
```

`run`と`errors`の2つありました。`run`はinputの値をもらって`Result`を返しています。`errors`はエラーだけ取れるようになっていますね。
これで`Decoder input err a`の使い方がわかりました。とにかくエラーが欲しいときは`errors`、変換したいときは`run`を使えばよいです

## プリミティブなデコーダー

このモジュールはデコーダーなので最初に簡単なIntやStringのデコーダーを探しましょう

[ここら辺です](https://package.elm-lang.org/packages/arowM/elm-form-decoder/latest/Form-Decoder#int)

```elm
int : err -> Decoder String err Int
float : err -> Decoder String err Float
```

intとfloatがあります。この2つはどちらもStringから各数値型への変換するデコーダーになっています
おそらくString.toInt, toFloatで変換していて、失敗した場合は引数のerrを返す実装になっているのでしょう(実装の確認は読者への課題とする)

これだけ見るとString.toIntなどで直接変換すればいいやって気分になりますね。
Primitive decodersのところにあるのはあとは以下の3つです

```elm
always : a -> Decoder input never a
identity : Decoder input never input
fail : err -> Decoder input err a
```

あれれーstringがないぞー
とわたしはなりました。`identity`が相当です
`Decoder input never input`の型変数をみてみましょう。入力と出力が同じ型でエラーは`never`になっています。つまり入力の値を失敗せずそのまま出力にすると読めます

> あくまでそう読めるというだけで`never`は失敗しないというのを型的に保証するものではありません。慣習的な表現です。これに限らず引数で固定されない型変数は`Never`入れちゃってもいいんですが変換するのが面倒なので`Never`を入れてないだけです

identityをStringに限定するとこうなります

```elm
string : Decoder String never String
string = identity
```
これでStringからStringに変換する（何もしない）デコーダーです

何もしないで受け流す、つまりidentityですね

あとの2つは定数的なものです
`always`は受け取った`a`を変換結果にするデコーダー、`fail`は受け取った`err`で失敗するデコーダーになります

これでプリミティブなデコーダーは終わりです

## `custom`

`custom`はカスタムって名前ですがデコーダーの定義そのままです。こわくないよ

```elm
custom : (input -> Result (List err) a) -> Decoder input err a

type Decoder input err a
    = Decoder (input -> Result (List err) a)
```

見ての通りそのままですね。フォームデコーダーは失敗するかもしれない変換です

## バリデーター

elm-form-decoderには`Decoder`のほかにもう1つ型が定義されています

```elm
type alias Validator input err =
    Decoder input err ()
```

さっきから変換だ変換だと書いていますがフォームといえばバリデーションです。
elm-form-decoderの`Validator`は`Decoder`の変換後の型がないという形で定義されています
つまりinputを受け取って変換はせずにある処理に成功か失敗するかをみています

```elm
validator : Int -> Result String ()
validator num =
    if num >= 0 then
        Ok ()
    else
        Err "0以上の整数でなければなりません"
```

きっとこんな感じですね

### 基本的なバリデーター

4つ用意されています

```elm
minBound : err -> comparable -> Validator comparable err
maxBound : err -> comparable -> Validator comparable err

minLength : err -> Int -> Validator String err
maxLength : err -> Int -> Validator String err
```

4つとも形はほぼ一緒ですね。最初の`err`はそのバリデーターが失敗したときに出るエラー値です
`min/maxBound`は最小/最大値で値を制限します。`min/maxLength`はStringに対して最小/最大長を制限します

### バリデーターの適用方法

作ったバリデーターをデコーダーに適用することでインプットの値を変換しつつバリデーションもするデコーダーを作ることができます

```elm
assert : Validator a err -> Decoder input err a -> Decoder input err a

type Error
    = Invalid
    | TooSmall
    | TooBig

myDecoder : Decoder String Error Int
myDecoder =
    int Invalid
        |> assert (minBound TooSmall 3)
        |> assert (maxBound TooBig 6)
```

デコーダーにパイプして`assert`でバリデーターを適用します
これで文字列を3以上6以下の整数に変換するデコーダーができました

`assert`の型定義から、バリデーターが検証する`a`の型はデコーダーの変換後の型だとわかります
デコーダーで変換したあとにバリデーターでその値を検証するという感じです

バリデータの関数には他に`when`と`unless`があります

```elm
when : (a -> Bool) -> Validator a err -> Validator a err
unless : (a -> Bool) -> Validator a err -> Validator a err
```

ある条件を満たす（または満たさない）ときだけバリデーターを適用させる、という変換ですね

## デコーダーを組み立てる（ここから実践編）

デコーダーで大事なのは小さなデコーダーを組み合わせて大きなデコーダーを作れることです
（あと変換と検証を統一的に扱えることです）

```elm
type alias Form =
    { name : String, age : String }

type alias Person =
    { name : String, age : Int }
```

Form型からPerson型へのデコーダーを定義してみましょう

### 各フィールドのデコーダーを作る

いきなりForm型からPerson型へのデコーダーを作るのは大変なので、まずnameとageをそれぞれ変換するデコーダーを作りましょう。まず型定義から考えます

```elm
nameDecoder : Decoder String err String
ageDecoder : Decoder String err Int
```

`nameDecoder`はStringからStringへの変換、`ageDecoder`はStringからIntへの変換です
変換前と後の型を考えたところで制約や失敗するかどうかを考えます

- 未入力はだめ
- StringからIntへの変換で失敗する
- 負の年齢はだめ

これをエラー型にしましょう

```elm
type Error
  = Required
  | IntInvalid
  | MinusInt
```

ネーミングセンスについては置いておきましょう。これらを上から作っていきます

```elm
notEmpty : Validator String Error
notEmpty =
    minLength Required 1


myInt : Decoder String Error Int
myInt =
    int IntInvalid


notMinus : Validator Int Error
notMinus =
    minBound MinusInt 0
```

できました。では名前と年齢のデコーダーを作りましょう

```elm
nameDecoder : Decoder String Error String
nameDecoder =
    Form.Decoder.identity
        |> assert notEmpty


ageDecoder : Decoder String Error Int
ageDecoder =
    myInt
        |> assert notMinus
```

できました。（ここの実装に関して問題があるのですが段階を踏んでいる都合上これでいきます。続編で書きます）

### Form型に"あげる"

各フィールドのデコーダーを作ったのでこれを組み合わせてFormからPersonへのデコーダーを作りたいです
ということで`mapN`関数を使って組み合わせましょう

```elm
decoder_ =
    map2 Person
        nameDecoder
        ageDecoder
```

できました！

嘘です、これは罠です。`Decoder Form Error Person`という型を付けると以下のエラーが出ます

```elm
Something is off with the body of the `decoder_` definition:

This `map2` call produces:

    Decoder String Error Person

But the type annotation on `decoder_` says it should be:

    Decoder Form Error Person
```

定義と型注釈の型があってませんと言われています
入力の型がFormじゃなくてStringみたいです。今までの実装にFormでてきてませんからね……順当です

ということで入力の型をあげましょう。`lift`を使います

```elm
lift : (j -> i) -> Decoder i err a -> Decoder j err a
```

感覚的には入力の型を上にあげる感じです
今回の場合各フィールドから上のForm型にあげていきます

```elm
nameDecoder_ : Decoder Form Error String
nameDecoder_ =
    lift .name nameDecoder


ageDecoder_ : Decoder Form Error Int
ageDecoder_ =
    lift .age ageDecoder
```

nameとageのデコーダーの入力の型をFormにあげました
（liftの定義的に.name、.ageだけでは型が定まりませんが型注釈でFormに固定してます）

> `.name`の型は`{ b | name : a } -> a`です。`a`はStringで固定されるので`{ b | name : String } -> String`ですね。`b`は`lift`だけでは固定されません。型注釈で固定してます

Formから対象の値へのgetterをliftに渡すと入力の型がずれます

> デコーダーは`input -> Result (List err) a`なので`form -> field`と`field -> Result (List err) a`があれば`form -> Result (List err) a`が作れます
>
> ```elm
get : form -> field
fieldDecoder : field -> Result (List err) a
>
formDecoder : form -> Result (List err) a
formDecoder form =
    fieldDecoder (get form)
```
>
> mapは慣れているかと思いますがliftってなんだよってひともわかりましたか？わたしはフォームデコーダーでわかりました

入力の型をFormにしたので組み合わせてみましょう

```elm
decoder : Decoder Form Error Person
decoder =
    map2 Person
        nameDecoder_
        ageDecoder_
```

こんどは大丈夫です。これで完成しました！

### パイプでつなげて構成する

フォームデコーダーには別スタイルのAPIも用意されています

```elm
decoder2 : Decoder Form Error Person
decoder2 =
    top Person
        |> field nameDecoder_
        |> field ageDecoder_
```

json-decode-pipelineでお馴染みの形ですね
フォームデコーダーでは`map5`までしかないのでそれ以上つなげたいときはこっちのスタイルで書くことになります

> この`top`は`always`と同じです。定義も一緒です。json-decode-pipelinでは`succeed`を使いますね

## 他の関数

### `andThen`と`with`

ちょっと難しい関数に`andThen`と`with`の2つがあります

```elm
with : (i -> Decoder i err a) -> Decoder i err a
andThen : (a -> Decoder input x b) -> Decoder input x a -> Decoder input x b
```

`andThen`はMaybeなどにもあるのでわかるんじゃないかと思います。結果の値に依存したデコーダーをつなげる関数です
`with`はひとに説明できるほどわかっていないのですが、入力の値に依存したデコーダーを作る関数です

`andThen`と`with`は逆の関係にみえます。あんまりよくわかんないんですけど

### `pass`

from 1.2.0

```elm
pass : Decoder b x c -> Decoder a x b -> Decoder a x c
```

デコーダーをシーケンシャルにつなげていく関数です。aからbに変換したあとbからcへのデコーダーでcに変換します。つまりaからcへのデコーダーになります
結果を次に「渡す」の意味っぽいです

> <blockquote class="twitter-tweet"><p lang="ja" dir="ltr">クイズ: なんでErrになるか<br><br>import Form.Decoder as Decoder exposing (Decoder) <br><br> pre : Decoder String Error String<br>pre = Decoder.custom &lt;| Ok &lt;&lt; String.filter (\c -&gt; c /= &#39;,&#39;)<br><br>Decoder.run (pre |&gt; Decoder.andThen (\_ -&gt; Decoder.float Invalid)) &quot;3,000&quot;<br>--&gt; Err [Invalid]</p>&mdash; 🐐ヤギ魔法少女さくらちゃん（Kadzuya Okamoto） (@arowM_) <a href="https://twitter.com/arowM_/status/1141328563401920514?ref_src=twsrc%5Etfw">June 19, 2019</a></blockquote>
>
> ユーザーからこの手の失敗を報告されたから作ったみたいです。これつまり最初の`pre`で変換した値を捨てているのが問題です。ただの`Decoder.float`になってしまっています
これを回避するには`String.toFloat`を直接使ったり、`run`をかまして`custom << run Decoder.float Invalid`ってすればいいんですがちょっとやりにくいですね
ということで`pass`は`andThen`だけでも原理的にできるけどちょっと大変なのでできましたという感じです

### その他

```elm
list : Decoder a err b -> Decoder (List a) err (List b)
array : Decoder a err b -> Decoder (Array a) err (Array b)
```

inとoutの両方を`List`/`Array`にします

```elm
mapError : (x -> y) -> Decoder input x a -> Decoder input y a
```

エラーを変換します
今まででてきた組み合わせ関数ではerrの型は同じにしないといけませんでした。

## 終わり

elm-form-decoderの関数を紹介してきました

ここから今のわたしのきっちりレベルでフォームを作ろうかと思ったんですがちょっと気力と紙幅の関係上、次に持ち越します