我不明白类型类在 GHC 中是如何工作的。
好的,考虑这个函数:
linear :: Num x => x -> x -> x -> x
linear a b x = a*x + b
这需要三个数字作为输入,并返回一个数字作为输出。此函数接受任何数字类型;它是多态的。GHC 是如何实现的?好吧,本质上,编译器创建了一个“类字典”,其中包含所有类方法(在本例中为+
、-
、*
等)。该字典成为函数的额外隐藏参数。像这样的东西:
data NumDict x =
NumDict
{
method_add :: x -> x -> x,
method_subtract :: x -> x -> x,
method_multiply :: x -> x -> x,
...
}
linear :: NumDict x -> x -> x -> x -> x
linear dict a b x = a `method_multiply dict` x `method_add dict` b
每当您调用该函数时,编译器都会自动插入正确的字典 - 除非调用函数也是多态的,在这种情况下,它本身会收到一个字典,所以只需传递它即可。
事实上,缺乏多态性的函数通常更快,并不是因为缺少函数查找,而是因为知道类型可以进行额外的优化。例如,我们的多态linear
函数将适用于数字、向量、矩阵、比率、复数,任何东西。现在,如果编译器知道我们想在 上使用它,那么Double
现在所有的操作都变成了单个机器代码指令,所有的操作数都可以在处理器寄存器中传递,等等。所有这些都会产生非常高效的代码。即使它是带有分量的复数Double
,我们也可以让它变得漂亮和高效。如果我们不知道我们会得到什么类型,我们就无法进行任何优化......这就是大多数速度差异通常来自的地方。
对于像线性这样的小函数,它很可能在每次调用时都会被内联,从而不会产生多态开销和少量的代码重复——就像 C++ 模板一样。对于更大、更复杂的多态函数,可能会有一些成本。一般来说,编译器会决定这一点,而不是你 - 除非你想开始在这个地方洒 pragma。;-) 或者,如果你实际上不使用任何多态性,你可以只给所有单态类型签名......