Haskell就是抽象。但是抽象会花费额外的CPU周期和额外的内存使用量,因为所有抽象(多态)数据的常见表示 - 堆上的指针。有一些方法可以使抽象代码更好地满足高性能需求。据我了解,它的一种方式是专业化 - 基本上是额外的代码生成(手动或编译器),对吗?
让我们假设下面的所有代码都是严格(这有助于编译器执行更多优化?)
如果我们有一个函数sum
:
sum :: (Num a) => a -> a -> a
我们可以使用specialize
pragma:
{-#SPECIALIZE sum :: Float -> Float -> Float#-}
现在,如果haskell编译器可以在编译时确定我们在两个sum
上调用Float
,那么它将使用它的专用版本。没有堆分配,对吧?
功能 - 完成。相同的pragma可以应用于类实例。逻辑在这里不会改变,是吗?
但是数据类型呢?
我怀疑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
上具有多态性)。在这种情况下,最佳做法是什么?
答案 0 :(得分:1)
正如您所说,我们无法在UVec a
上模式匹配,而不知道a
是什么。
一种选择是使用另一个使用自定义函数扩展vector类的类型类。
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
特定代码。
答案 1 :(得分:0)
部分答案,但也许可能有所帮助。
据我了解,其中一种方法是专业化 - 基本上是额外的代码生成(手动或编译器),对吗?
是的,这类似于C ++模板中的代码实例化。
现在如果haskell编译器可以在编译时确定我们在两个Floats上调用sum,那么它将使用它的专用版本。没有堆分配,对吧?
是的,编译器会尽可能调用专用版本。不确定你对堆分配的意思。
关于依赖类型向量:通常(我从Idris知道这个),编译器尽可能消除向量的长度。它旨在进行更强大的类型检查。在运行时,长度信息是无用的,可以删除。