Haskell is all about abstraction. But abstraction costs us extra CPU cycles and extra memory usage due to common representation of all abstract (polymorphic) data - pointer on heap. There are some ways to make abstract code play better with high performance demands. As far as I understand, one way it is done is specialization - basically extra code generation(manual or by compiler), correct ?
Let's assume that all code below is Strict(which helps compiler perform more optimizations ?)
If we have a function sum
:
sum :: (Num a) => a -> a -> a
We can generate specialized version of it using specialize
pragma:
{-#SPECIALIZE sum :: Float -> Float -> Float#-}
Now if haskell compiler can determine at compile time that we call sum
on two Float
s, it is going to use specialized version of it. No heap allocations, right ?
Functions - done. Same pragma can be applied to class instances. Logic does not change here, does it ?
But what about data types ?
I suspect that TypeFamilies
are in charge here ?
Let's try to specialize dependent length-indexed list.
--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
But Vec
has a problem. We can't pattern match on its constructors as
each instance of UVec
does not have to provide Vec
with identical constructors. This forces us to implement each function on Vec
for each instance of Vec
(as lack of pattern matching implies that it can't be polymorphic on Vec
). What is the best practice in such case ?