I want to use unboxed vectors on a simple newtype, but it's not clear to me how to enable this. What I have so far:
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeFamilies #-}
module UnboxedTest where
import Data.Vector.Unboxed
import Data.Word
newtype X = X Word64 deriving (Unbox)
I get:
src/UnboxedTest.hs:11:32: error:
• No instance for (Data.Vector.Generic.Base.Vector Vector X)
arising from the 'deriving' clause of a data type declaration
Possible fix:
use a standalone 'deriving instance' declaration,
so you can specify the instance context yourself
• When deriving the instance for (Unbox X)
|
11 | newtype X = X Word64 deriving (Unbox)
How do I enable this to be put inside an unboxed vector?
CodePudding user response:
It is pretty simple if there is an isomorphic type that can already be stored inside unboxed vectors. For Point
that is (Word64, Word64)
.
You have to do as that documentation page says and write:
{-# LANGUAGE TypeFamilies #-}
import Data.Vector.Unboxed
import Data.Vector.Unboxed.Mutable (MVector)
import qualified Data.Vector.Generic as G
import qualified Data.Vector.Generic.Mutable as GM
import Data.Word
data Point = Point Word64 Word64
newtype instance MVector s Point = MV_Point (MVector s (Word64,Word64))
newtype instance Vector Point = V_Point (Vector (Word64,Word64))
instance GM.MVector MVector Point where
{-# INLINE basicLength #-}
basicLength (MV_Point v) = GM.basicLength v
{-# INLINE basicUnsafeWrite #-}
basicUnsafeWrite (MV_Point v) i (Point x y) = GM.basicUnsafeWrite v i (x, y)
...
instance G.Vector Vector Point where
{-# INLINE basicLength #-}
basicLength (V_Point v) = G.basicLength v
{-# INLINE basicUnsafeIndexM #-}
basicUnsafeIndexM (V_Point v) i = do
(x, y) <- G.basicUnsafeIndexM v i
pure (Point x y)
...
instance Unbox Point
These declarations specify how your Point
can be converted to a (Word64, Word64)
to be stored in the unboxed vector. It becomes slightly more obvious that these are not completely redundant if you look at for example the basicUnsafeWrite
and basicUnsafeIndexM
function where you can see that you actually do need to write some code to convert Point
to (Word64, Word64)
and back.
CodePudding user response:
Although Haskell's various deriving mechanisms are still not able to generate instances of classes pertaining to a type family (like Unboxed.Vector
in this case), there's always the possibility to generate instances with Template Haskell, which can do basically anything you can do by hand. And fortunately, somebody has already written macros that generate Unbox
instances, in the vector-th-unbox
package:
{-# LANGUAGE TemplateHaskell, MultiParamTypeClasses, TypeFamilies, FlexibleInstances #-}
import Data.Vector.Unboxed.Deriving (derivingUnbox)
newtype X = X Word64
derivingUnbox "X"
[t| X -> Word64 |]
[| \(X w) -> w |]
[| X |]
Actually, since this is a newtype I'm tempted to suggest a macro that's even simpler to use:
import Data.Coerce
import Language.Haskell.TH
derivingNewtypeUnbox :: String -> TypeQ -> DecsQ
derivingNewtypeUnbox tfq tg = derivingUnbox tfq tg [|coerce|] [|coerce|]
And then it's just
derivingNewtypeUnbox "X" [t| X -> Word64 |]