creating custom instance of UArray
Asked Answered
M

1

7

Suppose I have a simple data type like:

data Cell = Open | Blocked

and I'd like to use a UArray Int Cell. Is there an easy way to do this? Can I somehow reuse the definition for UArray Int Bool?

Muzz answered 20/2, 2014 at 2:33 Comment(1)
Assuming you want to use an Unbox vector of Cells, you'd need instances of Unbox, Data.Mutable.MVector, and Data.Vector.Vector, plus two type family instances. This can be result a bit of nasty boilerplate, but it can be copied from the Unbox code for Bool. An alternative would be to make a Storable instance for Cell and use Storable vector. I'm not aware of any efficiency difference between the two vector types.Maciemaciel
M
16

This answer explains why Vectors are better than Arrays, so I'm going to give you the answer for unboxed vectors.

I did try deriving an MArray and IArray instance for Cell based on the Bool instances, but the Bool instances are quite complicated; it would be at least as ugly as manually deriving an Unbox instance for vectors. Unlike vectors, you also can't just derive Storable and use Storable arrays: you still need the Marray and IArray instances. There doesn't appear to be a nice TH solution yet, so you're better off using vectors for those reasons as well.

There are several ways to do this, some more painful than others.

  1. vector-th-unbox

    Pros: Straightforward, much shorter than manually deriving Unbox instances

    Cons: Requires -XTemplateHaskell

    {-# LANGUAGE TemplateHaskell, MultiParamTypeClasses, TypeFamilies #-}
    
    import Data.Vector.Unboxed
    import Data.Vector.Unboxed.Deriving
    import qualified Data.Vector.Generic
    import qualified Data.Vector.Generic.Mutable
    
    data Cell = Open | Blocked deriving (Show)
    
    derivingUnbox "Cell"
        [t| Cell -> Bool |]
        [| \ x -> case x of
            Open -> True
            Blocked -> False |]
        [| \ x -> case x of
            True -> Open
            False -> Blocked |]
    
    main = print $ show $ singleton Open
    
  2. Write your own Unbox, M.MVector, and V.Vector instances, plus two data instances

    {-# LANGUAGE TypeFamilies, MultiParamTypeClasses #-}
    
    import qualified Data.Vector.Generic            as V
    import qualified Data.Vector.Generic.Mutable    as M
    import qualified Data.Vector.Unboxed            as U
    import Control.Monad
    
    data Cell = Open | Blocked deriving (Show)
    
    data instance U.MVector s Cell = MV_Cell (U.MVector s Cell)
    data instance U.Vector Cell = V_Cell (U.Vector Cell)
    
    instance U.Unbox Cell
    
    {- purloined and tweaked from code in `vector` 
       package that defines types as unboxed -}
    instance M.MVector U.MVector Cell where
      {-# INLINE basicLength #-}
      {-# INLINE basicUnsafeSlice #-}
      {-# INLINE basicOverlaps #-}
      {-# INLINE basicUnsafeNew #-}
      {-# INLINE basicUnsafeReplicate #-}
      {-# INLINE basicUnsafeRead #-}
      {-# INLINE basicUnsafeWrite #-}
      {-# INLINE basicClear #-}
      {-# INLINE basicSet #-}
      {-# INLINE basicUnsafeCopy #-}
      {-# INLINE basicUnsafeGrow #-}
    
      basicLength (MV_Cell v) = M.basicLength v
      basicUnsafeSlice i n (MV_Cell v) = MV_Cell $ M.basicUnsafeSlice i n v
      basicOverlaps (MV_Cell v1) (MV_Cell v2) = M.basicOverlaps v1 v2
      basicUnsafeNew n = MV_Cell `liftM` M.basicUnsafeNew n
      basicUnsafeReplicate n x = MV_Cell `liftM` M.basicUnsafeReplicate n x
      basicUnsafeRead (MV_Cell v) i = M.basicUnsafeRead v i
      basicUnsafeWrite (MV_Cell v) i x = M.basicUnsafeWrite v i x
      basicClear (MV_Cell v) = M.basicClear v
      basicSet (MV_Cell v) x = M.basicSet v x
      basicUnsafeCopy (MV_Cell v1) (MV_Cell v2) = M.basicUnsafeCopy v1 v2
      basicUnsafeMove (MV_Cell v1) (MV_Cell v2) = M.basicUnsafeMove v1 v2
      basicUnsafeGrow (MV_Cell v) n = MV_Cell `liftM` M.basicUnsafeGrow v n
    
    instance V.Vector U.Vector Cell where
      {-# INLINE basicUnsafeFreeze #-}
      {-# INLINE basicUnsafeThaw #-}
      {-# INLINE basicLength #-}
      {-# INLINE basicUnsafeSlice #-}
      {-# INLINE basicUnsafeIndexM #-}
      {-# INLINE elemseq #-}
    
      basicUnsafeFreeze (MV_Cell v) = V_Cell `liftM` V.basicUnsafeFreeze v
      basicUnsafeThaw (V_Cell v) = MV_Cell `liftM` V.basicUnsafeThaw v
      basicLength (V_Cell v) = V.basicLength v
      basicUnsafeSlice i n (V_Cell v) = V_Cell $ V.basicUnsafeSlice i n v
      basicUnsafeIndexM (V_Cell v) i = V.basicUnsafeIndexM v i
      basicUnsafeCopy (MV_Cell mv) (V_Cell v) = V.basicUnsafeCopy mv v
      elemseq _ = seq
    
    main = print $ show $ U.singleton Open
    

    Wasn't that fun?

  3. Create a Storable instance and use Data.Vector.Storable instead.

    Pros: No TH, and relatively simple instance

    Cons: The instance is less obvious than the TH definition. Also, whenever you ask a SO question about Storable vectors, someone will inevitably ask why you aren't using Unboxed vectors, though no one seems to know why Unboxed vectors are better.

    For a data:

    {-# LANGUAGE ScopedTypeVariables #-}
    
    import Control.Monad
    import Data.Vector.Storable
    import Foreign.Storable
    
    import GHC.Ptr
    import GHC.Int
    
    -- defined in HsBaseConfig.h as 
    -- #define HTYPE_INT Int32
    type HTYPE_INT = Int32
    
    data Cell = Open | Blocked deriving (Show)
    
    instance Storable Cell where
     sizeOf _          = sizeOf (undefined::HTYPE_INT)
     alignment _       = alignment (undefined::HTYPE_INT)
     peekElemOff p i   = liftM (\x -> case x of 
                            (0::HTYPE_INT) -> Blocked
                            otherwise -> Open) $ peekElemOff (castPtr p) i
     pokeElemOff p i x = pokeElemOff (castPtr p) i $ case x of
        Blocked -> 0
        Open -> (1 :: HTYPE_INT)
    
    main = print $ show $ singleton Open
    

    Or for a newtype:

    {-# LANGUAGE GeneralizedNewtypeDeriving #-}
    
    import Data.Vector.Storable as S
    import Foreign.Storable
    
    newtype Cell = IsOpen Bool deriving (Show)
    
    main = print $ show $ S.singleton (Foo True)
    
  4. Unbox instances for newtype

    This doesn't directly apply to your question since you don't have a newtype, but I'll include it for completeness.

    Pros: No TH, no code to write, still using Unboxed vectors for the haters

    Cons: None?

    {-# LANGUAGE GeneralizedNewtypeDeriving, 
                 StandaloneDeriving, 
                 MultiParamTypeClasses #-}
    
    import Data.Vector.Generic as V
    import Data.Vector.Generic.Mutable as M
    import Data.Vector.Unboxed as U
    
    newtype Cell = IsOpen Bool deriving (Unbox, Show)
    deriving instance V.Vector U.Vector Cell
    deriving instance M.MVector U.MVector Cell
    
    main = print $ show $ U.singleton (IsOpen True)
    

    EDIT

    Note that this solution currently isn't possible in GHC 7.8.

Maciemaciel answered 20/2, 2014 at 4:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.