Smartphonemaoeのブログこの広告は、90日以上更新していないブログに表示しています。ATND向けに書いたものです。LaemmelさんがHaskellの型クラスを解説する動画を見つけました。Problemと呼ばれるプログラミングにおける古典的な問題をネタに、型クラスの基本的な使い方からfundepsの必要性まで、とてもわかりやすく解説しているのでお勧めです。今回は、このくそ忙しい師走に2時間も動画を見ていられないあなたのために、内容を抜粋して紹介したいと思います。Problemとは何かまあ慌てずに、まずはジミーウェールズに聞いてみましょう。encyclopediaニホンゴにすると、静的型付き言語で、再コンパイルなしで、新しいデータ型を追加したり、新しい関数を追加できるようにするにはどうしたらよいかということです。動画の中では非常に基本的なインタプリタを例に解説しています。それでは実際に見ていきましょう。対象のインタプリタは次のように振る舞うことを想定します。z42ConstやAddというデータ構成子を使って値を作り、prettyPrintで文字列に、evaluateで実際に計算した値を返します。このインタプリタはどう実装すればよいのでしょうか。types)を使えば簡単に実装できます。Exprプリティプリンタは次のように定義できます。rデータ型はそのままに、さらに関数を追加してみます。実際に計算するevaluateは次のように定義できます。variantsと呼ばれている)を追加してみましょう。Exprを追加するにはどうしたらよいでしょうか?もちろんExprの定義に追加することになります。Exprプリティプリンタはどうなるでしょうか。prettyPrintはExprの各データ構成子でパターンマッチしているので、新しく追加したNegにもマッチするように定義を修正する必要があります。e評価器evaluateも同様です。関数型言語(代数的データ型)ではデータ型を追加するのが難しい何が言いたいかというと、Exprにvariantを一つ追加するだけで、各所を変更する必要がある、ということです。これでは新しいvariantを追加するたびにモジュール全体を再コンパイルする必要があり、柔軟性に欠けているといえます。Calendar的にちょっと。やる気が(ryので省略します。一言でまとめるとOOPでは状況が逆で、データを追加するのは簡単ですが、操作を追加するのが難しくなります。Problemとは静的型を使っていて再コンパイルすることなくデータ型や操作を増やすのが難しいということです。大事なことでもないのに2回言いました。ここまでが一つ目の動画です。この問題をHaskellでどう解決しますか?という話が次の動画で解説されています。型クラスは魔法の道具?今までみてきたインタプリタの実装は、代数的データ型で実装されたExprに新たなvariantを追加すると、すべての関数でパターンマッチを追加する必要がありました。datatype)と表現しています。datatype)はどのように実装できるでしょうか?それはHaskellでクールなものトップ3を挙げたら必ずランクインしそうな型クラスです。型クラスとは2つ目の動画では型クラスの基本について丁寧に説明されています。Javaに慣れ親しんでいる方はインタフェースにちょっと似ている、型で多重ディスパッチする仕組みと考えてください。datatypeそれでは型クラスを使って開いたデータ型を実装します。ここでは発想を変えてConstとAddを別々のデータ型として定義し、それぞれをExprクラスのインスタンスにします。r)Exprクラスはメソッド一つも持ちません。これは与えられたデータ型がExprのインスタンスであることのみが重要ということです。プリティプリンタは次のように定義されます。r関数を追加するのもPrettyPrintクラスと同様、簡単です。rさらに新しくデータ型を追加するのも簡単です。xを追加してみましょう。xという新しい表現が使えるようになりました。重要なのはNegの定義自体はExprと同じモジュールである必要がないことです。これにより他のモジュールは再コンパイルなしで新しいデータ型を追加できるようになります。classesを使う動画ではさらに1歩進めて、複数の型の関係性を扱う場合も取り上げています。具体例として図形の重なりを判定することを考えます。閉じた代数的データ型で定義してみましょう。}重なりを判定する閉じた関数intersectは次のようになるでしょう。......ここでShapeにvariantを追加したらどうなるでしょうか?先ほどと同じですね。intersectのパターンマッチを追加してやる必要があります。Ellipseintersectもインタプリタと同様に次のように定義できます。...違いはIntersectクラスの定義でxとyという2つのパラメータを取っていることだけです。3つ以上のパラメータをとっても同じように適用できます。(fundeps)もう一つ例を考えてみましょう。今度は図形を正規化(normalize)します。面積と座標を保持したまま、RectangleはSquareに、EllipseはCircleに変換するようなものです。閉じた型で定義してみます。開いた型ではどうなるでしょうか。...こちらも簡単です。この定義は使うときになって問題が発覚します*1。3これは型推論が意図したとおりには動かないことが原因です。XXXというインスタンスは一つしかないので、型は一意に定まるように見えますが、Shapeは開かれていることを思い出してください。3)の型がSquareなのかCircleなのか判断がつきません。型システムはこのような問題を避けるように、型チェックで弾いてくれているのです。Circleという正規化は不要な時もあるでしょう。dependenciesです。「Normalizeの1つ目のパラメータまで決まったら、2つ目のパラメータは一意に定まる」という意図を型に含めるには、ほんの少しNormalizeの定義をいじってやります。s2」という部分が「1つ目のパラメータまで決まったら、2つ目のパラメータは一意に定まる」ことを表しています。3今度はうまく動きました。Circleを追加するとちゃんと型チェックで弾いてくれます。このようにfundepsは曖昧で一意に定まらない型に対して、制約を与えてやることで、型の表現力を増す働きをします。familiesという拡張もあって、fundepsをより関数的に書くために使うこともできます。こちらの紹介はまた別の機会に。Problemの型クラスを用いた解法はシンプルで素晴らしいものですが、万能ではありません。installで泣いた人も多いはずなどの問題があります。conversionはここら辺の問題をうまく扱っていたような気がします。Haskellでもいい方法ないかなあ。notesの前半で取り上げられています。偏りのないコインを投げつづけよう。