我的计算最好被描述为向量上的迭代突变;最终结果是向量的最终状态。
我认为,实现这种功能的“惯用”方法是在“修改”时简单地传递一个新的矢量对象。所以你的迭代方法是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)
,使用“不纯的”“可变”矢量,而不是在每一步传递新的修改后的矢量。
答案 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