Home > Mobile >  Unboxed Vectors for simple data types
Unboxed Vectors for simple data types

Time:11-04

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 |]
  • Related