Maybe
Elmをよく書くようになるとMaybe
型を非常に頻繁にみるようになります。Maybe
は以下のように定義されています:
type Maybe a
= Just a
| Nothing
-- Just 3.14 : Maybe Float
-- Just "hi" : Maybe String
-- Just True : Maybe Bool
-- Nothing : Maybe a
Maybe
は2つのバリアントを持つ型です。つまり何も持っていない(=Nothing
)か、ちょうど(=Just
)1つの値を持っているか、です。Maybe a
の型変数は具体的な値次第でMaybe Float
やMaybe String
といった型を持つことを可能にします。
Maybe
には主に2つの使いみちがあります。部分関数と入力が任意のフィールドで役立ちます。
部分関数
ある入力に対しては答えを与えるが他には与えない関数が欲しい場合があります。多くの人がそういう関数に遭遇するのは、ユーザからの入力を数値に変換しようとしてString.toFloat
関数を使おうとしたときでしょう。実際に動いているところを見てみましょう。
String.toFloat
を他のいろいろな文字列で呼び出してみて、何が起きるか見てみてください⬆️
すべての文字列が数値として意味をなすわけではありません。この関数はそれを明示的に表現しています。文字列をfloatに変換できますか?多分(=Maybe)!変換したら、そのデータをパターンマッチして適切に処理を続けましょう。
演習: 摂氏から華氏に変換する小さなプログラムをここに書きました。さまざまな方法で
view
コードをリファクタリングしてみてください。無効な入力の周りに赤い枠を付けることはできますか?他の変換を追加することができますか。華氏から摂氏?インチからメートル?
入力が任意のフィールド
もう1つのMaybe
の値がよく出てくる場所は入力が任意のフィールドを持つレコードの中です。
例えば、人がつながったり、友達になったりするSNSを運営しているとします。たとえば The spiel のようなものです。The onion は2011年ごろに「私達が目指す本当のゴールはCIAのためにできるだけ多くのデータを採掘することだ」とその概要を記事で公開しました。もし世の中の あらゆる データが欲しいのなら、人々をまずSNSに引き込む必要があります。情報は後から設定できるようにしましょう。SNSに参加した人たちが自然に自分から情報をどんどんシェアしたくなってしまうような機能をつけるのです。
それでは単純なユーザのモデルから始めましょう。ユーザは必ず名前を持たなければなりません。しかし年齢は省略可能にしようと思います。
type alias User =
{ name : String
, age : Maybe Int
}
スー(Sue)がアカウントを作ることにしたが、誕生日は提供しないと決めたとしましょう:
sue : User
sue =
{ name = "Sue", age = Nothing }
スーの友達は彼女の誕生日を祝うことはできません。彼女の友達は誕生日を知らずにスーを大切にできるのだろうか……。後にトム(Tom)がプロフィールを作成し、 年齢を設定しました 。
tom : User
tom =
{ name = "Tom", age = Just 24 }
素晴らしい、これで誕生日にはいいことがあるでしょう。しかしもっと重要なことは、トムが今や貴重な人口統計データの一部になったことです。広告主は喜ぶでしょう。
さて、今ではユーザが何人かいます。法律を破らずにアルコールを販売するにはどうしたらいいでしょうか。もし21歳未満の人に売り出そうとしたら、人々はおそらく怒り狂うでしょう。なので年齢をちゃんと確認しましょう:
訳注: アメリカでは一部を除き飲酒可能年齢は21歳からです。
canBuyAlcohol : User -> Bool
canBuyAlcohol user =
case user.age of
Nothing ->
False
Just age ->
age >= 21
Maybe
型はユーザの年齢を使うときにパターンマッチするのを強いることに注意してください。ユーザが年齢を持っていない可能性があることを忘れたようなコードを書くことはElmでは実際に不可能です。Elmはそれを保証します!これで未成年者に直接影響を与えていないと確信してアルコールを宣伝できます。成人済みのユーザにだけ宣伝します。
使いすぎを避ける
このMaybe
型は非常に便利ですが、限界があります。 たとえカスタム型でエラーを表現するのがより適切な場合であっても、初心者は特にMaybe
型に興奮して、いたるところでそれを使用する傾向があります。
例えば、友達と競争するエクササイズアプリがあるとします。最初に友達の名前のリストを取ってきておいて、必要になったら後からその友達のフィットネス情報をロードするようにできます。この状況を以下のようにモデリングしたくなるかもしれません:
type alias Friend =
{ name : String
, age : Maybe Int
, height : Maybe Float
, weight : Maybe Float
}
すべての情報がそこに表現されていますが、特定のアプリケーションがどう動作するかを本当にモデリングしていません。代わりに以下のようにモデリングする方がはるかに正確です:
type Friend
= Less String
| More String Info
type alias Info =
{ age : Int
, height : Float
, weight : Float
}
この新しいモデルはアプリケーションについてもっと多くのことを捉えています。実際の状況は2つしかありません。名前だけを持っているか、あるいは名前とたくさんの情報を持っているかのどちらかです。viewのコードでは、もはや単にFriend
型のLess
かMore
のどちらのviewを表示するかについて考えるだけです。“私が年齢を持っていて体重を持っていない場合はどうすればいいのか”というような疑問に答える必要はありません。そのような状況はこの、より正確な型では不可能です!
ポイントは、どこかでMaybe
を使っているのを見つけたら、より正確な表現を見つけられるかどうかみるためにtype
とtype alias
を使った型の定義を調べる価値があるということです。これはしばしばupdateとviewのコードにたくさんの素晴らしいリファクタリングを導きます!
余談:
null
参照と関連して
null
参照の発明者であるTony Hoareは、null
参照を次のように述懐しました。それは10億ドル相当の過ちだったと私は思います。それとは1965年のnull参照の発明です。当時、私はオブジェクト指向言語(ALGOL W)での参照のための最初の包括的な型システムを設計していました。私の目標は、コンパイラが自動的にチェックを実行して参照の使用がすべて絶対に安全であると保証することです。しかし、私はnull参照を入れるという誘惑に耐えることができませんでした。それは単に実装がとても簡単だったからです。これにより、無数のエラー、脆弱性、およびシステムクラッシュが発生し、過去40年間でおそらく数十億ドルもの痛みと損害が発生しています。
その設計は失敗を 暗黙的 にします。あなたが
String
を持っていると思っているときは、いつだって代わりにnull
を持っているだけなのかもしれません。あなた自身がnullかどうか確認すべきなのでしょうか?それともその値を作成した人が事前にnullでないことを確認しておいてくれたのでしょうか?多分大丈夫?サーバーをクラッシュするかもしれない?それらのリスクは実際に(突然クラッシュしたりして)問題としてあらわれて初めて気づくでしょう!Elmは
null
参照をまったく持たないことでこれらの問題を回避します。代わりに失敗を 明示的 にするためにMaybe
のようなカスタム型を使用します。この方法に驚くようなことは何もありません。String
は常にString
であり、Maybe String
があればコンパイラは両方のバリアントが適切に処理されていることを保証します。このようにしてnull参照と同じ柔軟性を得られますが、突然クラッシュするようなことはありません。