うん、私もこれについて考えました。基本的には、Haskell 98を実装できるように見えますが、ボクシングによるポリモーフィズムの代わりにマルチインスタンス化によるポリモーフィズムを使用して、言語拡張機能の一部を実装することはできないようです。
いくつかのHaskell機能をC++ライブラリとして実装しようとすると、これについての洞察を得ることができます(ご存知のように、C ++はポリモーフィズムによるマルチインスタレーションを実行します)。あなたが見つけたのは、ポリモーフィック関数への参照を含むポリモーフィック値を持つことが不可能であることを除いて、Haskellができることはすべてできるということです。
これがどのように見えるかは、
template<typename T>
void f(T); // f :: a -> IO ()
特定のインスタンス化のアドレスを取得して、実行時に関数ポインターとして渡すことができます。
&f<int>
ただし、テンプレートのアドレスを取得することはできません(&f
)。これは理にかなっています。テンプレートは純粋にコンパイル時の構成です。また、マルチインスタンス化によってポリモーフィズムを実行している場合、特定のインスタンス化へのポインターを持つことはできますが、マシンコードレベルではポリモーフィズム関数自体へのポインターを持つことはできません。
では、Haskellはどこで多形値を使用するのでしょうか?一見すると、「明示的なforallを記述しなければならない場所ならどこでも」という経験則が適切であるように思われます。したがってPolymorphicComponents
、、、、およびRank2Types
は明らかなノーノーです。これをC++に変換することはできません。RankNTypes
ImpredicativeTypes
data MkList = MkList (forall a. a -> [a])
singleton = MkList (\x -> [x])
一方、ExistentialQuantification
少なくともいくつかの場合に実行可能です。これは、テンプレートコンストラクター(または、より一般的には、コンストラクターがクラス自体よりも多くのものにテンプレート化されているクラス)を持つ非テンプレートクラスを持つことを意味します。
Haskellの場合:
data SomeShow = forall a. Show a => SomeShow a
instance Show SomeShow where show (SomeShow a) = show a
これは、C++で次のように実装できます。
// a function which takes a void*, casts it to the given type, and
// calls the appropriate show() function (statically selected based
// on overload resolution rules)
template<typename T>
String showVoid(void *x)
{
show(*(T*)x);
}
class SomeShow
{
private:
void *m_data;
String (*m_show)(void*); // m_show :: Any -> String
public:
template<typename T>
SomeShow(T x)
: m_data(new T(x)) // memory management issues here, but that's orthogonal
, m_show(&showVoid<T>)
{
}
String show()
{
// alternately we could declare the top-level show() as a friend and
// put this there
return m_show(m_data);
}
};
// C++ doesn't have type classes per se, but it has overloading, which means
// that interfaces are implicit: where in Haskell you would write a class and
// instances, in C++ you just write a function with the same name for each type
String show(SomeShow x)
{
return x.show();
}
どちらの言語でも、ポリモーフィックコンストラクターを持つ非ポリモーフィック型があります。
実装できる言語拡張機能と実装できない言語拡張機能があることを示しましたが、コインの裏側についてはどうでしょうか。Haskell98に実装できないものはありますか?forallを書くためにも言語拡張()が必要であるという事実から判断するExplicitForAll
と、答えはノーだと思うでしょう。そして、あなたはほとんど正しいでしょうが、2つのしわがあります:型クラスと多形再帰です。型クラスは通常、ディクショナリの受け渡しを使用して実装されます。各インスタンス宣言により、関数のレコードが生成され、必要な場所に暗黙的に渡されます。
たとえば、モナドの場合、次のようになります。
data MonadDict m = MonadDict {
return :: forall a. a -> m a,
(>>=) :: forall a b. m a -> (a -> m b) -> m b
}
さて、あなたはそれらのすべてを見てください!それらを明示的に書くことはできませんが、辞書を渡す実装では、Haskell 98でも、ポリモーフィックメソッドを持つクラスはポリモーフィック関数を含むレコードになります。マルチインスタンスを使用してすべてを実装しようとしている場合、これは明らかに問題になります。Haskell 98に固執する場合、インスタンスはほとんどの場合グローバルで静的に認識されているため、辞書を渡すことなくほとんど逃げることができます。各インスタンスはいくつかのポリモーフィック関数を生成しますが、どちらを呼び出すかはコンパイル時にほとんど常にわかっているため、実行時にそれらへの参照を渡す必要はほとんどありません(これはできないので良いことです)。トレードオフは、プログラム全体のコンパイルを行う必要があることです。そうしないと、インスタンスが静的に認識されなくなるためです。それらは別のモジュールにある可能性があります。また、例外は多態的な再帰です。これには、実行時に辞書を作成する必要があります。を参照してください詳細については、他の回答をご覧ください。多形再帰は、型クラスがなくてもマルチインスタンス化アプローチを無効にします。sに関するコメントを参照してくださいBTree
。(またExistentialQuantification
、ポリモーフィックメソッドへのポインターの格納を再度開始する必要があるため、ポリモーフィックメソッドを使用する* plus *クラスは実行できなくなりました。)