わざわざ学んでみたらElmのMaybe型が楽しかった

**[わざわざ学ばなくてもいい言語ランキング1位の「Elm」をわざわざ学んでみる](https://qiita.com/Yametaro/items/89dc2950ec0b7d949fce)**の続編ですわ。

## 前回のあらすじ
コンパイルしてJavaScriptに変換することができる**AltJS的**な純粋関数型言語**Elm**、その基礎文法を覚えたワイ。
Hello Worldの次は、簡単な双方向バインディングに挑戦することにしたが・・・？

## 型が楽しくなってきたワイ
ワイ「ちゃんと型を定義すると、ミスが発生しにくくなるからええな」
ワイ「例えば`takashi`の`age`に`36`を代入しようとした時に」
ワイ「間違って`hage`とかタイポしてしまったとしても」

> **型の不一致**
ヒント：レコードフィールドの間違いのようです。
たぶん`hage`は`age`であるべきでは？

ワイ「とかいって、Elmコンパイラが**分かりやすく指摘してくれる**からな」
ワイ「間違ったことしてたらそもそも先に進めへん」
ワイ「せやから、実際にプログラムを動かしてみる前にエラーを治せてしまうんやな」
ワイ「Elmは**実行時エラーが出ない**ってのが売りやもんな」

## 型について疑問
ワイ「型を定義したら、その通りの値しか代入できひんってのは分かったけど」
ワイ「ユーザーが変な値を入力してきてもうたらどうなんねやろ」
ワイ「例えばワイは今回」

> ユーザーが`input`タグに**数値**を入力したら、
その**数値**が他のDOMにも表示される

ワイ「↑こういう、いわゆる**双方向バインディング**をしてみたいんや」
ワイ「せやから、**数値だけを入力して欲しい**んや」
ワイ「でも、もしユーザが」

> 文字列を入力してみるンゴwww

ワイ「とかいう**文字列**を入力してきたら、どうなんねやろ」
ワイ「ワイがいくら`userInputNumber`は`Int`型や！とか定義してても」
ワイ「実際ユーザが**何を入力してくるかは分からへん**もんな」
ワイ「やっぱ文字列を入力されたら、流石にエラー出てしまうんか・・・？」
ワイ「いや、でもElmでは**実行時エラーは出ない**らしいしな・・・」

ワイ「静的型付言語は、実行前にコードを静的に検査して」
ワイ「不正なコードがあればコンパイルエラーで教えてくれるけど」
ワイ「ユーザが**どんな値を入力してくるか分からん**のに」
ワイ「どうやってそれを**事前に検査**できるんやろ・・・」
ワイ「気になるから、とりあえずやってみよか」

## `view`に`input`タグを設置
ワイ「えーと、`input`タグに何かが入力されたら」
ワイ「それを検知して何らかの処理を走らせればええから・・・」

```elm
view model =
    div []
        [ input [ onInput UserInput ] [] ]
```

ワイ「`view`の中身は↑こうやな！」

```elm
input [ onInput UserInput ] []
```

ワイ「↑この一番左の`input`は、`input`タグを生成してくれや、ってことや」
ワイ「ほんで`[ onInput UserInput ]`いうのは」
ワイ「ユーザが何か入力したら`UserInput`というメッセージを送ってくれや、ってな意味や」
ワイ「そのメッセージはどこに送られるかというと」
ワイ「`update`という関数に送られるんや」
ワイ「せやから次は`update`いう関数を定義せなあかん」
ワイ「`UserInput`というメッセージが来たら」
ワイ「どんな風に状態を更新するか、それを定義していくで」

```elm
update msg model =
    case msg of
        UserInput inputString ->
            { model | userInputNumber = String.toInt inputString }
```

ワイ「↑こうやな」
ワイ「`UserInput`いうメッセージが来た場合は」
ワイ「`model`の`userInputNumber`に」
ワイ「ユーザが入力した数値を入れてやんねん」

ワイ「ただ、ユーザが入力した数値は`"3"`とか`"8"`とかいう文字列のはずやから」
ワイ「`String.toInt`いう関数で**Stirng型**から**Int型**に変換せなあかんねん」
ワイ「`userInputNumber`には**Int型の値しか入れられへん**からな」
ワイ「ちゃんと変換してあげればコンパイラちゃんも安心や」

> **型の不一致**
`userInputNumber`フィールドをこのように更新することはできません。
`toInt`呼び出しは以下を生成します。
> > `Maybe Int`

ワイ「**ファッ！？**」
ワイ「コンパイルエラー出てもうた」
ワイ「`String.toInt`は文字列を`Int`型に変換してくれるんちゃうんか」
ワイ「`Maybe Int`型にしてしまうんか」
ワイ「何や、**たぶん整数**て」

#### ピー君「だって数値に変換できひんかもんしれんやん」

ワイ「おぉ、**インコ**の**ピー君**やないか」
ワイ「**数値に変換できひんかもんしれへん**てどういうこと？」

ピー君「お前も言うてたけど、ユーザが」

> 文字列を入力してみるンゴwww

ピー君「とかいう**文字列**を入力してくるかもしれへんやん」
ピー君「数値に変換できる文字列ばかりとは限らんわけや」
ピー君「せやから`String.toInt`いう関数は`Maybe Int`型の値を生成するんや」

ワイ「分かったわ」
ワイ「ほな`userInputNumber`の定義を変えるわ」

```elm
type alias Model =
    { userInputNumber : Maybe Int
    }
```

ワイ「こうやな」
ワイ「`userInputNumber`は`Maybe Int`の値が入るべきですよ、と」

ワイ「ありゃ、またエラーが出たで」

> **型の不一致**
`init`定義の本体に何か問題があります。

ワイ「状態の初期値を定義する`init`に問題があんのかいな」

```elm
init : Model
init =
    { userInputNumber = 0
    }
```

ワイ「ああ、そうか」
ワイ「さっき`userInputNumber`は`Maybe Int`型にすることにしたから」
ワイ「`Int`型の`0`という数値を入れたらアカンのか」

ワイ「`Maybe Int`型の初期値って、何を入れたらええんや・・・」

ピー君「`Maybe Int`型の値は」
ピー君「`Just 0`とか`Just 5`みたいな値か」
ピー君「`Nothing`のどちらかや」

ワイ「ほえ〜」
ワイ「`0`やなくて`Just 0`なんやな」
ワイ「ほな`init`の中身は」

```elm
init =
    { userInputNumber = Just 0
    }
```

ワイ「↑こうやな」
ワイ「ほんまや、エラー消えたわ」
ワイ「これで、ユーザが何か入力するたびに、その値が`Model`に格納されるわけやな」
ワイ「ほなピー君、その新しい値を**画面に表示**するにはどうすんの？」

ピー君「`view`関数の中に`model.userInputNumber`って書くだけや」

ピー君「ユーザの入力をきっかけに」
ピー君「`UserInput`いうメッセージが`update`関数に届けられると、」
ピー君「`update`関数が新しい**状態**を作るやろ？」
ピー君「そうすると自動的に再度`view`関数が実行されるんや」
ピー君「せやから`view`関数の中に」
ピー君「`model.userInputNumber`を表示するためのタグを書いとけばええんや」

ワイ「なるほどな」
ワイ「でも、一つ気に何なんねんけど・・・」
ワイ「その`Maybe Int`とか言う値はさあ」
ワイ「**数値やないかもしれへん**ねやろ？」
ワイ「だって、ユーザが」

> 文字列を入力してみるンゴwww

ワイ「とかいう**文字列**を入力してくるかもしれんわけやから」
ワイ「その場合は`model.userInputNumber`には何が入ってくるん」

ピー君「その場合は`Nothing`いう値が入ってくるわ」
ピー君「`String.toInt`関数で、`Just 3`とか`Just 8`とかに変換できひんかった場合は」
ピー君「`Nothing`いう値になんねん」

ワイ「そうなんか・・・」
ワイ「えぇ・・・でも・・・」
ワイ「**数値が入っとるかもしれへんし、何も入ってへんかもしれへん・・・**」
ワイ「そんな**[シュレーディンガーの猫](https://ja.wikipedia.org/wiki/%E3%82%B7%E3%83%A5%E3%83%AC%E3%83%BC%E3%83%87%E3%82%A3%E3%83%B3%E3%82%AC%E3%83%BC%E3%81%AE%E7%8C%AB)**みたいな値を画面に表示しようとしたら**なんかバグりそうやん**・・・」
ワイ「**`undefined`**とかになりそうやん・・・」

ピー君「とりあえずやってみい」

ワイ「とりあえずやるんかい」
ワイ「じゃあ、画面に表示するために`String.fromInt`言う関数を使って」
ワイ「`model.userInputNumber`を**文字列型**に変換して表示させるわ！」

```elm
p [] [ text (String.fromInt model.userInputNumber) ]
```

ワイ「こうや」
ワイ「pタグを生成して、その中で」
ワイ「**文字列に変換**した`model.userInputNumber`を表示や！」
ワイ「どや、コンパイラはん！？」

> **型の不一致** 
`fromInt`の最初の引数には`Int`が必要です。
`userInputNumber`の値は次のとおりです。
>  >Maybe Int

ワイ「せやろな」
ワイ「何となく分かってたわ」
ワイ「`fromInt`って名前の関数やもん」
ワイ「`Maybe Int`渡したらアカンわ」
ワイ「ほなどうしたらええんや・・・」

ワイ「この**シュレーディンガーの猫**みたいな」
ワイ「数値なのか何もないんか、**両方の可能性を持つ値**を」
ワイ「どうやって扱えばええんや・・・」

#### ピー君「caseを使って、両方の可能性に対応した処理を書くんや」

ワイ「両方の可能性に対応した処理・・・」
ワイ「よう分からんけど`case`を使ってやってみるわ」

```elm
case maybeInt of
    Just int ->
        String.fromInt int

    Nothing ->
        "数値以外はやめてや"
```

ワイ「こんな感じ？」
ワイ「数値やったら`String.fromInt`で文字列に変換するし」
ワイ「`Nothing`やったら`"数値以外はやめてや"`って文字列を返す」

ピー君「ええやん」
ピー君「そんな感じで`case`を使って」
ピー君「**シュレーディンガーの猫**ちゃんを**密閉状態の箱**の中から**出してあげるんや**」
ピー君「それを関数にしてみい」

ワイ「分かったわ」

```elm
maybeIntToString maybeInt =
    case maybeInt of
        Just int ->
            String.fromInt int

        Nothing ->
            "数値以外はやめてや"
```

ワイ「`Maybe Int`を引数に受け取って、文字列を返す関数やから」
ワイ「関数名は`maybeIntToString`や」
ワイ「型注釈でいうと`maybeIntToString : Maybe Int -> String`やな」

ピー君「その関数を使って`model.userInputNumber`を表示するんや」

ワイ「おー、だんだん分かってきたで」

```elm
p [] [ text (maybeIntToString model.userInputNumber) ]
```

ワイ「こういうことやな」

## 動かしてみる

ワイ「おお！」
ワイ「ようやくコンパイルが通ったで！」
ワイ「`input`タグに数値を入力すると、`p`タグにも同じ数値が表示されるし」
ワイ「`input`タグに`文字列を入力してみるンゴwww`って入力した場合は」
ワイ「`p`タグに`数値以外はやめてや`って表示されるわ」

ワイ「ちなみに`case`で片方のケースしか書かんかったらどうなんねやろ」
ワイ「`Nothing`の方を消してみよ」

```elm
maybeIntToString : Maybe Int -> String
maybeIntToString maybeInt =
    case maybeInt of
        Just int ->
            String.fromInt int
```

ワイ「おお、ちゃんとコンパイルエラーになるんやな」

> **欠けているパターン**
この`case`はすべての可能性のための分岐を持ちません：
行方不明の可能性が含まれます：
> > `Nothing`

ワイ「なるほどな〜」
ワイ「**シュレーディンガーの猫ちゃん**的な値を扱うときは」
ワイ「`case`を使って、**考えられ得る全てのパターンを網羅した処理**を書かんと」
ワイ「そもそもコンパイルが通らへんのか[^1]」
ワイ「こうすることで、ユーザ入力という**不確定性のある値**でさえも」
ワイ「**事前に検査**できるわけか」
ワイ「**むっちゃオモロイやーん**」

## 感想
ワイ「型注釈を書くと、**JSDocsに相当するような情報がコード上に残っていく**ことになるから」
ワイ「後からコードを読んだときに意味が分かりやすいな」
ワイ「しかもその注釈の通りに**コンパイラが監視**して、間違ってたら**分かりやすく教えてくれる**・・・」
ワイ「ただの注釈やなくて、**強制力のある注釈**ってわけや」

ワイ「あと`case`とか`Maybe`型がオモロイな」
ワイ「`Maybe`型という、そのままでは表示できない**変な型**を持った値」
ワイ「何が入ってるか普通には見えへん、**密閉された箱に入ったような値**」

ワイ「そういう値は`case`で**全ケースの処理を網羅**せんと、**中の値を取り出すことができひん**[^1]」
ワイ「それによって、**シュレディンガーの猫ちゃん**的な**不確定な値**も」
ワイ「**事前にバッチリ検査**できんねんな」
ワイ「そら**実行時エラーも起きませんわ**」
ワイ「さすが、**言語ランキング1位**の**Elm**ちゃんや」
ワイ「どういうランキングやったっけな・・・」
ワイ「ちょっと調べてみよ」

> **2019年にわざわざ学ばなくてもいいプログラミング言語**
> **総合1位: Elm**

ワイ「**学ばなくていいんかい！**」
ワイ「**めっちゃ学んでもうたわ！！！**」

ワイ「・・・ていうか」
ワイ「こんな素晴らしい言語が**ワースト1位**って・・・」

#### ワイ「世界どうなってんねん！！！」

**〜おしまい〜**


## 追記1: 今回のコード
**[Ellie](https://ellie-app.com/5QgwKMvTPjna1)**で見れるから好きにいじってみてや。

## 追記2： ビギナーにお勧めのElm記事たち
**[Elmで体験する関数型言語の面白さ](https://qiita.com/pxfnc/items/542e0f6705182a9f93ad)**
**[VS Code で Elm の開発環境を構築する on Windows 10](https://qiita.com/ossan_pg/items/5c3ce00b74036139507e)**
**[値が制約を満たしていることを型で保証する](https://qiita.com/arowM/items/0106f2b66a6a446bad38)**

## 追記3: 後日談
ワイ「ところで**インコ**の**ピー君**は」
ワイ「いつから**会話ができるようになった**んやろ」
ワイ「インコってオウム返し、もとい**インコ返し**しかできひんはずやん」

#### ハスケル子「私が吹き込んでおきました」

ワイ「おお」
ワイ「天才中学生インターンの**ハスケル子**ちゃん・・・」

ハスケル子「やめ太郎さんがElmの勉強してるなあ、と思って」
ハスケル子「**あらかじめ想定される疑問について録音しておきました**」

ワイ「そうなんか・・・**相変わらず神がかってるな**・・・」
ワイ「でもハスケル子ちゃん・・・」
ワイ「**録音て**」
ワイ「**ピー君**をテープレコーダーみたいにいうたらアカンで」

ハスケル子「テープレコーダーって何ですか」

ワイ「・・・んん**ジェネレーションギャップ！！！**」

**〜おしまい〜**

[^1]: ほかにも[withDefault](https://package.elm-lang.org/packages/elm/core/latest/Maybe#withDefault)とかでも値を取り出せます