Reputation: 3218
In Haskell is possible to write functions over a size indexed list that ensure that we never get out of bounds. A possible implementation is:
data Nat = Zero | Succ Nat deriving (Eq, Ord, Show)
infixr 5 :-
data Vec (n :: Nat) a where
Nil :: Vec 'Zero a
(:-) :: a -> Vec n a -> Vec ('Succ n) a
data Fin (n :: Nat) where
FZ :: Fin ('Succ n)
FS :: Fin n -> Fin ('Succ n)
vLookup :: Vec n a -> Fin n -> a
vLookup Nil _ = undefined
vLookup (x :- _) FZ = x
vLookup (_ :- xs) (FS i) = vLookup xs i
Of course, this is just fine for immutable size indexed lists (aka Vec
).
But how about mutable ones? Is possible to define (or is there a library for) mutable size indexed arrays in Haskell? If there's no such library, how it could be implemented?
Edit 1: I searched Hackage and didn't found any library matching my description (size indexed mutable arrays).
Edit 2: I would like to mention that I have thought use IORef
's to get the desired mutability:
type Array n a = IORef (Vec n a)
but I'm wondering if there's a better (more efficient, more elegant) option...
Upvotes: 2
Views: 191
Reputation: 120711
Such a type does exist on Hackage.
I would avoid something like type Array n a = IORef (Vec n a)
. Mutable arrays are all about efficiency. If you don't need it to run fast / with low memory footprint, then there's not much point in using them – even “mutable algorithms” are generally easier to express in Haskell using functional style, perhaps with a state monad but no true destructive mutable state.
But if efficiency matters, then you also want tight cache-efficient storage. Unboxed vectors are ideal. OTOH, Vec
is on the runtime data level no different from ordinary lists, which are of course not so great in terms of cache coherence. Even if you defined them to actually interweave mutability into the list spine, it wouldn't be really better in any way than using immutable Vec
s in a pure-functional style.
So, if I had to write something like that simple, I'd rather wrap an (unsafe, length-wise) unboxed mutable arrox in a length-indexed newtype.
import qualified Data.Vector.Unboxed.Mutable as VUM
newtype MVec (s :: *) (n :: Nat) (a :: *)
= MVec { getMVector :: VUM.MVector s a }
You can then define an interface that makes all public operations type-checked length-safe, while still retaining the performance profile of MVector
.
Upvotes: 7