How would you make a single interface to various implementations of mutable arrays and vectors, while keeping it pragmatic? I.e. with:
- performance comparable to direct use of arrays/vectors (without abstraction)
- minimum code duplication
Something like this:
-- (s)torage (k)ey (v)alue (m)onad
class (Monad m) => Storage s k v m where
empty :: k -> v -> m (s k v)
alter :: (v -> v) -> k -> s k v -> m ()
fold :: (a -> v -> a) -> a -> s k v -> m a
main :: IO ()
main = do
_a <- empty 100 False :: IO (IOUArray Int Bool)
_b <- empty 100 42 :: IO (VUM.MVector RealWorld Int)
let _c = empty 100 False :: ST s (STUArray s Int Bool)
let _d = empty 100 42 :: ST s (VUM.MVector s Int)
return ()
The implementation for MArray can be something like this (seems working):
instance (Monad m, MArray s v m, Ix k, Num k) => Storage s k v m where
empty k v = A.newArray (0, k - 1) v
alter f k s = A.readArray s k >>= A.writeArray s k . f
fold f a s = foldl' f a <$> A.getElems s
Here's a broken implementation for MVectors:
-- doesn't work
instance (PrimMonad m, MVector s v, Unbox v) => Storage s k v m where
empty k v = VUM.replicate k v
alter f k s = VUM.modify s f k
fold f a s = VU.foldl' f a <$> VU.freeze s
Question:
- how would you implement an instance for MVector or refactor this code to make it possible to have an abstraction for both, arrays and vectors?
Imports and extensions:
{-# LANGUAGE FlexibleContexts, FlexibleInstances, MultiParamTypeClasses,
RankNTypes #-}
module UnifiedArray where
import Control.Monad.Primitive (PrimMonad, PrimState)
import Control.Monad.ST (RealWorld, ST, runST)
import Data.Array.IO (IOUArray)
import Data.Array.MArray (Ix, MArray)
import qualified Data.Array.MArray as A
import Data.Array.ST (STUArray)
import Data.Foldable (foldl')
import Data.Vector.Generic.Mutable (MVector)
import qualified Data.Vector.Unboxed as VU
import Data.Vector.Unboxed.Mutable (Unbox)
import qualified Data.Vector.Unboxed.Mutable as VUM
Runnable source code: https://gist.github.com/oshyshko/6fae4f859b8f8434af6d3f1eb3cad98e