Haskell中的“持久”不纯(IO)向量,具有类似数据库的持久接口

时间:2013-06-22 06:32:34

标签: haskell

我的计算最好被描述为向量上的迭代突变;最终结果是向量的最终状态。

我认为,实现这种功能的“惯用”方法是在“修改”时简单地传递一个新的矢量对象。所以你的迭代方法是operate_on_vector :: Vector -> Vector,它接收一个向量并输出修改后的向量,然后再通过该方法提供。

这个方法非常简单,我在实现它时没有任何问题,甚至是Haskell的新手。

或者,可以将所有这些封装在State monad中,并将不断重新创建和修改的向量作为状态值传递。

然而,我遭受了巨大的巨大性能成本,因为这些计算非常密集,迭代很多(大约数百万)并且数据向量可以变得非常大(大约数千个基元的数量级) 。在迭代的每一步重新创建内存中的新向量似乎相当昂贵,数据收集与否。

然后我考虑了IO是如何工作的 - 它可以看作基本上像State,除了状态值是“世界”,它不断变化。

也许我可以使用类似IO的东西来“操作”“世界”?而“世界”将成为记忆中的载体?有点像数据库查询,但一切都在内存中。

例如你可以用io

do
  putStrLn "enter something"
  something <- getLine
  putStrLine $ "you entered " ++ something

可以看作“执行”putStrLn并“修改”World对象,返回一个新的World对象并将其提供给下一个函数,该函数查询世界对象以查找由此产生的字符串修改,然后在另一次修改后返回另一个世界对象。

是否有类似的东西可以为可变载体做到这一点?

do
  putInVec 0 9          -- index 0, value 9
  val <- getFromVec 0
  putInVec 0 (val + 1)

,使用“不纯的”“可变”矢量,而不是在每一步传递新的修改后的矢量。

1 个答案:

答案 0 :(得分:8)

我相信你可以使用mutable vector和一个瘦的包装器在Reader + ST(或IO)monad上执行此操作。

它看起来像这样:

type MyVector = IOVector $x  -- Use your own elements type here instead of $x
newtype VectorIO a = VectorIO (ReaderT MyVector IO a) deriving (Monad, MonadReader, MonadIO)
-- You will need GeneralizedNewtypeDeriving extension here

-- Run your computation over an existing vector
runComputation :: MyVector -> VectorIO a -> IO MyVector
runComputation vector (VectorIO action) = runReaderT action vector >> return vector

-- Run your computation over a new vector of the specified length
runNewComputation :: Int -> VectorIO a -> IO MyVector
runNewComputation n action = do
  vector <- new n
  runComputation vector action

putInVec :: Int -> $x -> VectorIO ()
putInVec idx val = do
  v <- ask
  liftIO $ write v idx val

getFromVec :: Int -> VectorIO $x
getFromVec idx = do
  v <- ask
  liftIO $ read v idx

这就是全部。您可以使用VectorIO monad来执行计算,就像您在示例中所希望的那样。如果您不想要IO但需要纯计算,则可以使用ST monad;修改上面的代码将是微不足道的。

<强>更新

这是一个基于ST的版本:

{-# LANGUAGE GeneralizedNewtypeDeriving, FlexibleInstances, MultiParamTypeClasses, Rank2Types #-}
module Main where

import Control.Monad
import Control.Monad.Trans.Class
import Control.Monad.Reader
import Control.Monad.Reader.Class
import Control.Monad.ST
import Data.Vector as V
import Data.Vector.Mutable as MV

-- Your type of the elements
type E = Int

-- Mutable vector which will be used as a context
type MyVector s = MV.STVector s E

-- Immutable vector compatible with MyVector in its type
type MyPureVector = V.Vector E

-- Simple monad stack consisting of a reader with the mutable vector as a context 
-- and of an ST action
newtype VectorST s a = VectorST (ReaderT (MyVector s) (ST s) a) deriving Monad

-- Make the VectorST a reader monad
instance MonadReader (MyVector s) (VectorST s) where
    ask = VectorST $ ask
    local f (VectorST a) = VectorST $ local f a
    reader = VectorST . reader

-- Lift an ST action to a VectorST action
liftST :: ST s a -> VectorST s a
liftST = VectorST . lift

-- Run your computation over an existing vector
runComputation :: MyVector s -> VectorST s a -> ST s (MyVector s)
runComputation vector (VectorST action) = runReaderT action vector >> return vector

-- Run your computation over a new vector of the specified length
runNewComputation :: Int -> VectorST s a -> ST s (MyVector s)
runNewComputation n action = do
  vector <- MV.new n
  runComputation vector action

-- Run a computation on a new mutable vector and then freeze it to an immutable one
runComputationPure :: Int -> (forall s. VectorST s a) -> MyPureVector
runComputationPure n action = runST $ do
  vector <- runNewComputation n action
  V.unsafeFreeze vector

-- Put an element into the current vector
putInVec :: Int -> E -> VectorST s ()
putInVec idx val = do
  v <- ask
  liftST $ MV.write v idx val

-- Retrieve an element from the current vector
getFromVec :: Int -> VectorST s E
getFromVec idx = do
  v <- ask
  liftST $ MV.read v idx