Как отметил пользователь @ user2407038, сначала проще ограничиться бетоном MVector
типа IOVector
. Если вы специализируетесь типа new
, read
и write
к IOVector
, вы получите следующее, характерный не-пугающие типов:
new :: Int -> IO (IOVector a)
read :: IOVector a -> Int -> IO a
write :: IOVector a -> Int -> a -> IO()
Так для первой версии, реализация ваших Matrix
операций проста:
import Data.Vector.Mutable as V
import Control.Monad (liftM)
data Matrix a = Matrix { size :: (Int, Int), buffer :: IOVector a }
newMatrix :: (Int, Int) -> IO (Matrix a)
newMatrix (w, h) = liftM (Matrix (w, h)) $ V.new (w * h)
set :: (Int, Int) -> a -> Matrix a -> IO()
set pos e mtx = V.write (buffer mtx) (offset mtx pos) e
get :: (Int, Int) -> Matrix a -> IO a
get pos mtx = V.read (buffer mtx) (offset mtx pos)
offset :: Matrix a -> (Int, Int) -> Int
offset (Matrix (w, _h) _) (x, y) = w * y + x
Итак, как мы обобщаем выбор s
в MVector s
? Matrix
сами должен быть обобщены по выбору s
:
data Matrix s a = Matrix { size :: (Int, Int), buffer :: MVector s a }
И мы также должны пронизывать это обобщение через ко всем функциям. Давайте посмотрим на newMatrix
в деталях; остальное можно оставить в качестве упражнения для читателя.
Если мы просто абстрактные на s
, newMatrix
становится
newMatrix :: (Int, Int) -> IO (Matrix s a)
newMatrix (w, h) = liftM (Matrix (w, h)) $ V.new (w * h) -- Same implementation as above
Однако, конечно, это не может быть правильным - мы не можем создать MVector s a
в IO
при любом выборе s
, только RealWorld
! Ожидаемо, проверки типов ловит это:
Couldn't match type `s' with `RealWorld'
`s' is a rigid type variable bound by
the type signature for newMatrix :: (Int, Int) -> IO (Matrix s a)
Expected type: s
Actual type: PrimState IO
Expected type: IO (MVector s a)
Actual type: IO (MVector (PrimState IO) a)
In the return type of a call of `new'
In the second argument of `($)', namely `new (w * h)'
Но предположим, что мы написали
newMatrix :: (Monad m) => (Int, Int) -> m (Matrix s a)
Это, в некотором смысле, даже хуже: теперь мы говорим, что при любом выборе m
и s
(независимо от друг друга!), мы можем построить Matrix s a
в m
. Ясно, что это не так.
Это где необходим PrimMonad
класс типов: она обеспечивает связь между PrimState m
, выбором s
для вектора манипулирует, и монада m
, где эта манипуляция возможна. newMatrix
таким образом, становится
newMatrix :: (PrimMonad m) => (Int, Int) -> m (Matrix (PrimState m) a)
newMatrix (w, h) = liftM (Matrix (w, h)) $ V.new (w * h) -- implementation is still the same!
Остальные операции могут быть набраны в аналогичным образом.
'newMatrix' должен будет иметь тип' (PrimMonad m, MVector va) => arg1 -> arg2 -> argN -> m (Matrix vsa) ', так как вам нужно будет использовать' Data. Vector.Generic.Mutable.new', чтобы его построить. – bheklilr
'PrimState' - это тип, связанный с' PrimMonad'. Есть только два экземпляра 'PrimMonad':' ST s' и 'IO'. Следовательно, 'PrimState' является либо' RealWorld', либо 's' (из' ST s'). Но эти вещи скрыты в ['Control.Monad.Primitive'] (http://hackage.haskell.org/package/primitive-0.2.1/docs/Control-Monad-Primitive.html#t:PrimMonad). – Zeta
Примечание: документация, которую вы связываете, является древней. Это 'vector 0.5' с 2010 года. Посмотрите на [' vector 0.10.12. * '] (Http://hackage.haskell.org/package/vector-0.10.12.2/docs/Data-Vector-Mutable.html), хотя тип по-прежнему в основном один и тот же. – Zeta