Harper( https://existentialtype.wordpress.com/2011/04/16/modules-matter-most/ )によると、型クラスは単に同じレベルの抽象化を提供していないようです。モジュールが提供していて、その理由を正確に理解するのに苦労しています。そして、そのリンクには例がないので、主な違いを理解するのは難しいです。モジュールと型クラスの間の変換方法に関する他の論文もあります( http://www.cse.unsw.edu.au/~chak/papers/modules-classes.pdf )が、これはプログラマーの観点からは、実装とは実際には何の関係もありません(一方が実行でき、もう一方がエミュレートできないことはないと言っているだけです)。
具体的には、 最初のリンク :
1つ目は、型が型クラスを正確に1つの方法で実装できると主張していることです。たとえば、型クラスの哲学によれば、整数は正確に1つの方法(通常の順序付け)で順序付けできますが、明らかに、関心のある多くの順序付け(たとえば、分割可能性による)があります。 2つ目は、型が型クラスを実装する方法を指定することと、型推論中にそのような指定をいつ使用するかを指定することの2つの別々の問題を混乱させることです。
どちらもわかりません。型はMLで複数の方法で型クラスを実装できますか?新しい型を作成せずに、例によって分割可能性によって整数をどのように順序付けますか? Haskellでは、データを使用するようなことをし、別の順序を提供するためにinstance Ord
を用意する必要があります。
そして2つ目は、Haskellでは2つが区別されていませんか? 「型推論中にそのような仕様を使用する必要がある場合」の指定は、次のように行うことができます。
blah :: BlahType b => ...
ここで、BlahTypeは型推論中に使用されるクラスであり、実装クラスではありません。一方、「型が型クラスを実装する方法」は、instance
を使用して行われます。
リンクが実際に何を言おうとしているのか説明できる人はいますか?モジュールが型クラスよりも制限が少ない理由がよくわかりません。
この記事の内容を理解するには、HaskellのMonoid
型クラスについて考えてみてください。モノイドは任意の型T
であり、関数mappend :: T -> T -> T
と単位元empty :: T
があります。
a `mappend` (b `mappend` c) == (a `mappend` b) `mappend` c
a `mappend` mempty == mempty `mappend` a == a
この定義に適合するHaskellタイプはたくさんあります。すぐに頭に浮かぶ1つの例は整数であり、次のように定義できます。
instance Monoid Integer where
mappend = (+)
mempty = 0
すべての要件が満たされていることを確認できます。
a + (b + c) == (a + b) + c
a + 0 == 0 + a == a
確かに、これらの条件は加算を超えるすべての数に当てはまるので、次のように定義することもできます。
instance Num a => Monoid a where
mappend = (+)
mempty = 0
これで、GHCiでは次のことができるようになりました。
> mappend 3 5
8
> mempty
0
特に注意深い読者(または数学のバックグラウンドを持つ読者)は、乗算を超える数のMonoid
インスタンスも定義できることに気付いたでしょう。
instance Num a => Monoid a where
mappend = (*)
mempty = 1
a * (b * c) == (a * b) * c
a * 1 == 1 * a == a
しかし今、コンパイラーは問題に遭遇します。 mappend
のどの定義を数値に使用する必要がありますか? mappend 3 5
は8
または15
と同じですか?それが決定する方法はありません。これが、Haskellが単一の型クラスの複数のインスタンスを許可しない理由です。ただし、問題はまだ残っています。 Monoid
のどのNum
インスタンスを使用する必要がありますか?どちらも完全に有効であり、特定の状況では意味があります。解決策はどちらも使用しないことです。 HackageでMonoid
を見ると、Monoid
のNum
インスタンス、またはInteger
、Int
、Float
、またはDouble
がないことがわかります。代わりに、Monoid
とSum
のProduct
インスタンスがあります。 Sum
とProduct
は次のように定義されています。
newtype Sum a = Sum { getSum :: a }
newtype Product a = Product { getProduct :: a }
instance Num a => Monoid (Sum a) where
mappend (Sum a) (Sum b) = Sum $ a + b
mempty = Sum 0
instance Num a => Monoid (Product a) where
mappend (Product a) (Product b) = Product $ a * b
mempty = Product 1
ここで、数値をMonoid
として使用する場合は、Sum
またはProduct
タイプのいずれかでラップする必要があります。使用するタイプによって、使用するMonoid
インスタンスが決まります。これは、記事が説明しようとしていたことの本質です。 Haskellの型クラスシステムには、複数のインスタンスから選択できるシステムは組み込まれていません。代わりに、スケルトンタイプでフープをラップおよびアンラップして、フープをジャンプする必要があります。これを問題と見なすかどうかは、HaskellとMLのどちらを好むかを決定する要因の大部分を占めています。
MLは、同じクラスとタイプの複数の「インスタンス」を異なるモジュールで定義できるようにすることで、これを回避します。次に、インポートするモジュールによって、使用する「インスタンス」が決まります。 (厳密に言えば、MLにはクラスとインスタンスはありませんが、署名と構造があり、ほぼ同じように機能します。詳細な比較については、 このペーパー を参照してください)。