Edited at

すごいHaskell、ハスケル子と学ぼう!

すごいHaskellたのしく学ぼう!」という本を読みました。

せっかく関数型言語の本を読んだので、今回は関西型言語を用いて、学んだ内容について綴っていきたいと思います。


ある日、某会社にて

ワイ「なぁ、ハスケル子ちゃん」

ワイ「Haskellの記事とか読んでると出てくる、モナドってアレ何なん?」

ハスケル子「型クラスですね」

ワイ「型クラス・・・」

ワイ「ごめん、ぜんぜん分からんわ」

ワイ「型クラスって何・・・?」

ハスケル子「型の分類みたいな感じです」

ハスケル子「classって、分類とか同類って意味を持っているので」

ワイ「型の分類・・・」

ワイ「型自体が分類みたいな感じなのに、それを更に分類すんの・・・?」

ワイ「どどどどういうこと・・・?」

ワイ「舌打ちせんといてや・・・」


Int型とFloat型って似てるよね

ハスケル子「えっと、じゃあ・・・」

ハスケル子「Int型とFloat型が似てるっていう感覚は分かりますか?」

ワイ「それは分かるで」

ワイ「両方とも、数値っぽい型やもん」

ハスケル子「そうですよね」

ハスケル子「型の分類として同じだな、って感じがしますよね」

ワイ「ああ、確かに」

ハスケル子「じゃあ・・・」

ハスケル子「その数値っぽいっていうのは、具体的にどんな感じですか?」

ワイ「ええと、数値っぽいっていうのは・・・」

ワイ「足したり引いたり、掛け算したりできる感じや」

ハスケル子「ですよね」

ハスケル子「それを明確に定義したものが型クラスです」

ハスケル子「Int型とFloat型は、同じNum型クラスに属してるんです」

ハスケル子「属しているというか、Num型クラスを実装しているんですけど」

ワイ「ほえ〜」

ワイ「まだよう分からんけど」

ワイ「数値型全般を表すNumっていう型クラスがあるんやね」

ハスケル子「そうです」


型クラスが定義しているもの

ハスケル子「Num型クラスの定義には」


(+), (*), (-) を実装すべし!


ハスケル子「みたいな事が書いてあります」

ハスケル子「つまり───」


足し算・掛け算・引き算ができること!


ハスケル子「───みたいなルールですね」

ハスケル子「そして、実際にInt型やFloat型はそのように実装されなければなりません」

ワイ「ほえ〜」

ワイ「型クラスは、型たちの振る舞いを定義してんねやな」

ワイ「この型たちはこうあるべし!って」

ハスケル子「その通りです」


じゃあモナドは?

ワイ「Num型クラスを実装したInt型Float型は」

ワイ「足し算や引き算ができる、という共通の特徴を持っている・・・」

ワイ「そこは何となく分かったわ」

ワイ「ほんで、さっきハスケル子ちゃん」

ワイ「モナドも型クラスやって言うてたやん?」

ハスケル子「はい」

ワイ「ほな、Monad型クラスっていうのがあるってこと?」

ハスケル子「そうです」

ハスケル子「正しくは・・・」

ハスケル子「Monad型クラスを実装した型たちのことをモナドって呼びます」

ワイ「ほえ〜」

ワイ「ほな、Monad型クラスに属する型たちは、どんな共通の特徴を持ってんの?」

ハスケル子「Monad型クラスを実装した型たちは」

ハスケル子「文脈を持った値を表現できます」


文脈を持った値・・・

ワイ「ごめん、ぜんぜん分からんわ」

ワイ「実際、どんな型があんの?」

ハスケル子「例えば、Maybe型とかList型とかがモナドですね」

ワイ「おお、Maybeか」

ワイ「Elmにもある型やね」

ハスケル子「はい、あれとほぼ同じです」

ハスケル子「Maybe型の値は、文脈を持ってるじゃないですか?」

ワイ「いや、それがよう分からんわ・・・」

ハスケル子「じゃあ、Maybe型ってどんな時に使います?」

ワイ「どんな時っていうか、Elmだと・・・」

ワイ「String.toIntっていう関数で、文字列を数値に変換しようとすると」

ワイ「Maybe Int型の値になってしまうわ」

ハスケル子「それは何故ですか?」

ワイ「それは、文字列は必ずしも数値に変換できるとは限らんからや」

ワイ「例えば"5"とか"80"とかいう文字列やったら数値に変換できるけど」

ワイ「お問い合わせフォームとかで、ユーザが"うんこ"とか入力してくるかもしれんから」

ワイ「数値に変換できんような文字列String.toInt関数に渡されてしまうこともあって」

ワイ「ちゃんと数値に変換できる確証はない」

ワイ「だからMaybe Int型の値になるんや」

ハスケル子「そうですね」

ハスケル子「つまりMaybe Int型の値っていうのは・・・」

ハスケル子「数値に変換できてるかもしれないし、できてないかもしれない・・・

ハスケル子「そういう、文脈を持った値ですよね」

ワイ「確かに・・・」


不確実な値・・・その「不確実な」という文脈を表現できる

ハスケル子「数値に変換できてなかった場合の処理case式などでちゃんと書いておかないと、コンパイルが通らない

ハスケル子「そういう制限を設けることで、Elmは実行時エラーが起こらないようになっていますよね?」

ワイ「ああ、せやな」

ワイ「ユーザ入力から受け取る値とか、そういう」

ワイ「実行時にならんとどんな値が入ってくるのか分からんような、不確定性のある値も」

ワイ「事前に検査して安全に扱える、っていう不思議な仕組みやったね」

ハスケル子「はい」

ハスケル子「文脈に包まれた値を表現できることで」

ハスケル子「なんかシュレーディンガーの猫みたいな値すらも」

ハスケル子「事前コンパイルで検査できちゃう」

ハスケル子「そんな感じです」

ワイ「文脈ってハンパないな」

ハスケル子「そうなんです」


文脈に包まれた値の扱い方

ハスケル子「でも、値が文脈に包まれてるせいで面倒なこともあって」

ハスケル子「例えばMaybe Int型の値はそのままだと計算したりできないじゃないですか?」

ワイ「せやな」

ワイ「case式を使って」

ワイ「数値が入ってた場合と、入ってなかった場合の」

ワイ「両方の処理を書かないとあかんねん」

ハスケル子「で、そこを上手いことしてくれる関数がありますよね」

ハスケル子「ElmだとMaybe.mapとか」

ワイ「せやね」

ワイ「例えば、2つの整数を足すだけのaddっていう足し算関数があったとして」

ワイ「add関数が受け取れるのはInt型だけやから」

ワイ「Maybe Int型の値を足し算しようとしてもコンパイルエラーになってしまうねん」

ワイ「せやけどMaybe.map2っていう関数を使ってadd関数を変身させてあげると」

ワイ「Maybe Int型の値同士を足し算できる関数が出来あがんねん」

ハスケル子「関数の持ち上げってやつですね」

ワイ「あと、ElmにはMaybe.andThenっていう関数もあって」

ワイ「そのMaybe.andThenいう関数も」

ワイ「Int型しか受け取れん関数を」

ワイ「Maybe Intを受け取れる関数に変身させてくれたりするな」

ハスケル子「はい」

ハスケル子「値が文脈に包まれていてそのままでは計算できないから」

ハスケル子「そこを上手くやってくれる関数が用意されてるってことですね」


Haskellには>>=がある

ハスケル子「Haskellの場合だと、ElmのMaybe.andThen関数に相当する」

ハスケル子「>>=という演算子が用意されていて」

ハスケル子「その子が上手いことモナド絡みの処理を繋いでくれます」

ハスケル子「>>=バインドと読むんですけど」

ハスケル子「何か連続で処理をしているときに、途中で値が文脈に包まれてしまって

ハスケル子「あ、次の関数に渡すべき型とズレちゃった!」

ハスケル子「ってなりそうなときも」

ハスケル子「そこを上手いことハックしてくれるわけです」

ワイ「なるほどな〜」


まとめ


  • モナドは文脈に包まれた値を表現できる型

  • 文脈に包まれたままの値には、普通の関数を適用できない

  • でも、そこを上手いことハックする術>>=など)がちゃんと用意されている

  • モナドは>>=を実装すべし!ということはMonad型クラスに定義されている

ハスケル子「って感じですね」

ワイ「おおきにやで、ハスケル子ちゃん」

ワイ「なんとなく分かったわ」

〜おしまい〜