是的,我也想过这个问题。基本上,这个想法是你似乎可以实现 Haskell 98,但不能实现它的一些语言扩展,使用多态多实例化而不是多态多态。
您可以通过尝试将一些 Haskell 功能实现为 C++ 库来对此有所了解(正如您所注意到的,C++ 执行多态化多态化)。你发现你可以做 Haskell 能做的所有事情,除了不可能有多态值,包括对多态函数的引用。
这看起来像,如果你有
template<typename T>
void f(T); // f :: a -> IO ()
您可以在运行时将特定实例化的地址作为函数指针传递:
&f<int>
但您不能获取模板的地址 ( &f
)。这是有道理的:模板是纯粹的编译时构造。同样有意义的是,如果您通过多实例化进行多态性,则可以拥有指向任何特定实例化的指针,但不能拥有指向多态函数本身的指针,因为在机器代码级别,没有一个。
那么 Haskell 在哪里使用多态值呢?乍一看,一个好的经验法则似乎是“任何地方你必须写一个明确的 forall”。所以PolymorphicComponents
, Rank2Types
, RankNTypes
, 和ImpredicativeTypes
是明显的禁忌。你不能把它翻译成 C++:
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();
}
在这两种语言中,您都有一个带有多态构造函数的非多态类型。
所以我们已经证明了有些语言扩展可以实现,有些则不能,但是硬币的另一面呢:Haskell 98 中有什么是你不能实现的吗?从您甚至需要语言扩展 ( ) 来编写 forall 的事实来看ExplicitForAll
,您会认为答案是否定的。你几乎是对的,但有两个问题:类型类和多态递归。类型类通常使用字典传递来实现:每个实例声明都会产生一个函数记录,这些函数会在需要它们的地方隐式传递。
因此,例如,对于 Monad,您将拥有:
data MonadDict m = MonadDict {
return :: forall a. a -> m a,
(>>=) :: forall a b. m a -> (a -> m b) -> m b
}
那么你会看看那些foralls!您不能明确地编写它们,但是在字典传递实现中,即使在 Haskell 98 中,具有多态方法的类会导致包含多态函数的记录。如果您尝试使用多实例来实现整个事情,这显然会成为一个问题。您几乎可以不用字典传递就可以逃脱,因为如果您坚持使用 Haskell 98,实例几乎总是全局且静态已知的。每个实例都会产生一些多态函数,但是因为在编译时几乎总是知道要调用哪个函数,所以您几乎不需要在运行时传递对它们的引用(这很好,因为您不能)。权衡是您需要进行整个程序编译,否则实例不再是静态已知的:他们可能在不同的模块中。一个例外是多态递归,它实际上需要你在运行时建立一个字典。见其他答案以获取更多详细信息。即使没有类型类,多态递归也会杀死多实例化方法:请参阅关于BTree
s 的评论。(此外ExistentialQuantification
,具有多态方法的 *plus* 类不再可行,因为您将不得不再次开始存储指向多态函数的指针。)