Haskell(GHC)专业化旅游和高效TypeFamilies
Haskell完全是关于抽象。但是由于所有抽象(多态)数据的共同表示 - 堆中的指针,抽象花费我们额外的CPU周期和额外的内存使用。有一些方法可以使抽象代码更好地满足高性能需求。据我所知,一种方法是专业化 - 基本上额外的代码生成(手动或编译器),是正确的?Haskell(GHC)专业化旅游和高效TypeFamilies
让我们假设下面所有的代码是严格
如果我们有一个功能sum
(这有助于编译器进行更多的优化?):
sum :: (Num a) => a -> a -> a
我们可以通过specialize
产生它的专业版编译指示:
{-#SPECIALIZE sum :: Float -> Float -> Float#-}
现在如果haskell编译器可以在编译时确定我们在两个Float
之间调用sum
,它将使用它的专用版本。没有堆分配,对吧?
功能 - 完成。相同的杂注可以应用于类实例。逻辑在这里没有改变,是吗?
但是数据类型呢? 我怀疑TypeFamilies
在这里负责?
让我们尝试专门化依赖长度索引列表。
--UVec for unboxed vector
class UVec a where
data Vec (n :: Nat) a :: *
instance UVec Float where
data Vec n Float where
VNilFloat :: Vec 0 Float
VConsFloat :: {-#UNPACK#-}Float ->
Vec n Float ->
Vec (N :+ 1) Float
但Vec
有问题。我们无法对其构造函数进行模式匹配,因为 每个UVec
实例都不必为Vec
提供相同的构造函数。这迫使我们为Vec
的每个实例在Vec
上实现每个函数(因为缺少模式匹配意味着它不能在Vec
上多态)。这种情况下的最佳做法是什么?
正如你所说,我们不能在UVec a
上模式匹配,而不知道a
是什么。 一种选择是使用另一个类型类来扩展具有自定义函数的矢量类。
class UVec a => UVecSum a where
sum :: UVec a -> a
instance UVecSum Float where
sum = ... -- use pattern match here
如果,以后,我们使用sum v
其中v :: UVec Float
,我们在实例定义的Float
特异性代码将被调用。
部分答案,但也许可能有帮助。
据我所知,一种方法是专业化 - 基本上额外的代码生成(手动或编译器),正确吗?
是的,这与C++模板中的代码实例化类似。
现在,如果haskell编译器可以在编译时确定我们调用了两个浮点数sum,它将使用它的专用版本。没有堆分配,对吧?
是的,编译器尽可能调用专用版本。不确定你的意思是关于堆分配。
关于从属类型向量:通常(我知道这是来自Idris),在可能的情况下,编译器会消除向量的长度。它旨在用于更强大的类型检查。在运行时长度信息是无用的,可以被丢弃。
关于运行时长度信息:haskell没有完整的依赖类型。 Haskell在运行时值和类型之间有区别。所以Vec的长度保证被删除(因为长度是一种类型(或种类)而不是运行时值,但haskell不允许在运行时保存类型)。 – russoulmc