**わざわざ学ばなくてもいい言語ランキング1位の「Elm」をわざわざ学んでみる**の続編ですわ。
前回のあらすじ
コンパイルしてJavaScriptに変換することができるAltJS的な純粋関数型言語Elm、その基礎文法を覚えたワイ。
Hello Worldの次は、簡単な双方向バインディングに挑戦することにしたが・・・?
型が楽しくなってきたワイ
ワイ「ちゃんと型を定義すると、ミスが発生しにくくなるからええな」
ワイ「例えばtakashi
のage
に36
を代入しようとした時に」
ワイ「間違ってhage
とかタイポしてしまったとしても」
型の不一致
ヒント:レコードフィールドの間違いのようです。
たぶんhage
はage
であるべきでは?
ワイ「とかいって、Elmコンパイラが分かりやすく指摘してくれるからな」
ワイ「間違ったことしてたらそもそも先に進めへん」
ワイ「せやから、実際にプログラムを動かしてみる前にエラーを治せてしまうんやな」
ワイ「Elmは実行時エラーが出ないってのが売りやもんな」
型について疑問
ワイ「型を定義したら、その通りの値しか代入できひんってのは分かったけど」
ワイ「ユーザーが変な値を入力してきてもうたらどうなんねやろ」
ワイ「例えばワイは今回」
ユーザーが
input
タグに数値を入力したら、
その数値が他のDOMにも表示される
ワイ「↑こういう、いわゆる双方向バインディングをしてみたいんや」
ワイ「せやから、数値だけを入力して欲しいんや」
ワイ「でも、もしユーザが」
文字列を入力してみるンゴwww
ワイ「とかいう文字列を入力してきたら、どうなんねやろ」
ワイ「ワイがいくらuserInputNumber
はInt
型や!とか定義してても」
ワイ「実際ユーザが何を入力してくるかは分からへんもんな」
ワイ「やっぱ文字列を入力されたら、流石にエラー出てしまうんか・・・?」
ワイ「いや、でもElmでは実行時エラーは出ないらしいしな・・・」
ワイ「静的型付言語は、実行前にコードを静的に検査して」
ワイ「不正なコードがあればコンパイルエラーで教えてくれるけど」
ワイ「ユーザがどんな値を入力してくるか分からんのに」
ワイ「どうやってそれを事前に検査できるんやろ・・・」
ワイ「気になるから、とりあえずやってみよか」
view
にinput
タグを設置
ワイ「えーと、input
タグに何かが入力されたら」
ワイ「それを検知して何らかの処理を走らせればええから・・・」
view model =
div []
[ input [ onInput UserInput ] [] ]
ワイ「view
の中身は↑こうやな!」
input [ onInput UserInput ] []
ワイ「↑この一番左のinput
は、input
タグを生成してくれや、ってことや」
ワイ「ほんで[ onInput UserInput ]
いうのは」
ワイ「ユーザが何か入力したらUserInput
というメッセージを送ってくれや、ってな意味や」
ワイ「そのメッセージはどこに送られるかというと」
ワイ「update
という関数に送られるんや」
ワイ「せやから次はupdate
いう関数を定義せなあかん」
ワイ「UserInput
というメッセージが来たら」
ワイ「どんな風に状態を更新するか、それを定義していくで」
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
の定義を変えるわ」
type alias Model =
{ userInputNumber : Maybe Int
}
ワイ「こうやな」
ワイ「userInputNumber
はMaybe Int
の値が入るべきですよ、と」
ワイ「ありゃ、またエラーが出たで」
型の不一致
init
定義の本体に何か問題があります。
ワイ「状態の初期値を定義するinit
に問題があんのかいな」
init : Model
init =
{ userInputNumber = 0
}
ワイ「ああ、そうか」
ワイ「さっきuserInputNumber
はMaybe Int
型にすることにしたから」
ワイ「Int
型の0
という数値を入れたらアカンのか」
ワイ「Maybe Int
型の初期値って、何を入れたらええんや・・・」
ピー君「Maybe Int
型の値は」
ピー君「Just 0
とかJust 5
みたいな値か」
ピー君「Nothing
のどちらかや」
ワイ「ほえ〜」
ワイ「0
やなくてJust 0
なんやな」
ワイ「ほなinit
の中身は」
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
いう値になんねん」
ワイ「そうなんか・・・」
ワイ「えぇ・・・でも・・・」
ワイ「数値が入っとるかもしれへんし、何も入ってへんかもしれへん・・・」
ワイ「そんな**シュレーディンガーの猫みたいな値を画面に表示しようとしたらなんかバグりそうやん**・・・」
ワイ「**undefined
**とかになりそうやん・・・」
ピー君「とりあえずやってみい」
ワイ「とりあえずやるんかい」
ワイ「じゃあ、画面に表示するためにString.fromInt
言う関数を使って」
ワイ「model.userInputNumber
を文字列型に変換して表示させるわ!」
p [] [ text (String.fromInt model.userInputNumber) ]
ワイ「こうや」
ワイ「pタグを生成して、その中で」
ワイ「文字列に変換したmodel.userInputNumber
を表示や!」
ワイ「どや、コンパイラはん!?」
型の不一致
fromInt
の最初の引数にはInt
が必要です。
userInputNumber
の値は次のとおりです。Maybe Int
ワイ「せやろな」
ワイ「何となく分かってたわ」
ワイ「fromInt
って名前の関数やもん」
ワイ「Maybe Int
渡したらアカンわ」
ワイ「ほなどうしたらええんや・・・」
ワイ「このシュレーディンガーの猫みたいな」
ワイ「数値なのか何もないんか、両方の可能性を持つ値を」
ワイ「どうやって扱えばええんや・・・」
ピー君「caseを使って、両方の可能性に対応した処理を書くんや」
ワイ「両方の可能性に対応した処理・・・」
ワイ「よう分からんけどcase
を使ってやってみるわ」
case maybeInt of
Just int ->
String.fromInt int
Nothing ->
"数値以外はやめてや"
ワイ「こんな感じ?」
ワイ「数値やったらString.fromInt
で文字列に変換するし」
ワイ「Nothing
やったら"数値以外はやめてや"
って文字列を返す」
ピー君「ええやん」
ピー君「そんな感じでcase
を使って」
ピー君「シュレーディンガーの猫ちゃんを密閉状態の箱の中から出してあげるんや」
ピー君「それを関数にしてみい」
ワイ「分かったわ」
maybeIntToString maybeInt =
case maybeInt of
Just int ->
String.fromInt int
Nothing ->
"数値以外はやめてや"
ワイ「Maybe Int
を引数に受け取って、文字列を返す関数やから」
ワイ「関数名はmaybeIntToString
や」
ワイ「型注釈でいうとmaybeIntToString : Maybe Int -> String
やな」
ピー君「その関数を使ってmodel.userInputNumber
を表示するんや」
ワイ「おー、だんだん分かってきたで」
p [] [ text (maybeIntToString model.userInputNumber) ]
ワイ「こういうことやな」
動かしてみる
ワイ「おお!」
ワイ「ようやくコンパイルが通ったで!」
ワイ「input
タグに数値を入力すると、p
タグにも同じ数値が表示されるし」
ワイ「input
タグに文字列を入力してみるンゴwww
って入力した場合は」
ワイ「p
タグに数値以外はやめてや
って表示されるわ」
ワイ「ちなみにcase
で片方のケースしか書かんかったらどうなんねやろ」
ワイ「Nothing
の方を消してみよ」
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**で見れるから好きにいじってみてや。
追記2: ビギナーにお勧めのElm記事たち
Elmで体験する関数型言語の面白さ
VS Code で Elm の開発環境を構築する on Windows 10
値が制約を満たしていることを型で保証する
追記3: 後日談
ワイ「ところでインコのピー君は」
ワイ「いつから会話ができるようになったんやろ」
ワイ「インコってオウム返し、もといインコ返ししかできひんはずやん」
ハスケル子「私が吹き込んでおきました」
ワイ「おお」
ワイ「天才中学生インターンのハスケル子ちゃん・・・」
ハスケル子「やめ太郎さんがElmの勉強してるなあ、と思って」
ハスケル子「あらかじめ想定される疑問について録音しておきました」
ワイ「そうなんか・・・相変わらず神がかってるな・・・」
ワイ「でもハスケル子ちゃん・・・」
ワイ「録音て」
ワイ「ピー君をテープレコーダーみたいにいうたらアカンで」
ハスケル子「テープレコーダーって何ですか」
ワイ「・・・んんジェネレーションギャップ!!!」
〜おしまい〜
-
ほかにもwithDefaultとかでも値を取り出せます ↩ ↩2