io-streams-1.5.2.2: src/System/IO/Streams/Vector.hs
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE CPP #-}
-- | Vector conversions and utilities.
module System.IO.Streams.Vector
( -- * Vector conversions
fromVector
, toVector
, toVectorSized
, outputToVector
, outputToVectorSized
, toMutableVector
, toMutableVectorSized
, outputToMutableVector
, outputToMutableVectorSized
, writeVector
-- * Utility
, chunkVector
, vectorOutputStream
, vectorOutputStreamSized
, mutableVectorOutputStream
, mutableVectorOutputStreamSized
) where
------------------------------------------------------------------------------
import Control.Concurrent.MVar (modifyMVar, modifyMVar_, newMVar)
import Control.Monad (liftM, (>=>))
import Control.Monad.IO.Class (MonadIO (..))
import Control.Monad.Primitive (PrimState (..), RealWorld)
import Data.IORef (IORef, newIORef, readIORef, writeIORef)
import Data.Vector.Generic (Vector (..))
import qualified Data.Vector.Generic as V
import Data.Vector.Generic.Mutable (MVector)
import qualified Data.Vector.Generic.Mutable as VM
import System.IO.Streams.Internal (InputStream, OutputStream, fromGenerator, yield)
import qualified System.IO.Streams.Internal as S
#if MIN_VERSION_vector(0,13,0)
import Control.Monad.ST (stToIO)
#endif
basicUnsafeFreezeCompat :: Vector v a => V.Mutable v RealWorld a -> IO (v a)
#if MIN_VERSION_vector(0,13,0)
basicUnsafeFreezeCompat = stToIO . V.basicUnsafeFreeze
#else
basicUnsafeFreezeCompat = V.basicUnsafeFreeze
#endif
------------------------------------------------------------------------------
-- | Transforms a vector into an 'InputStream' that yields each of the values
-- in the vector in turn.
--
-- @
-- ghci> import "Control.Monad"
-- ghci> import qualified "System.IO.Streams" as Streams
-- ghci> import qualified "Data.Vector" as V
-- ghci> let v = V.'Data.Vector.fromList' [1, 2]
-- ghci> is <- Streams.'fromVector' v
-- ghci> 'Control.Monad.replicateM' 3 (Streams.'read' is)
-- ['Just' 1,'Just' 2,'Nothing']
-- @
fromVector :: Vector v a => v a -> IO (InputStream a)
fromVector = fromGenerator . V.mapM_ yield
{-# INLINE fromVector #-}
------------------------------------------------------------------------------
-- | Drains an 'InputStream', converting it to a vector. Note that this
-- function reads the entire 'InputStream' strictly into memory and as such is
-- not recommended for streaming applications or where the size of the input is
-- not bounded or known.
--
-- @
-- ghci> is <- Streams.'System.IO.Streams.fromList' [(1::Int)..4]
-- ghci> Streams.'toVector' is :: 'IO' (V.'Vector' Int)
-- fromList [1,2,3,4]
-- @
toVector :: Vector v a => InputStream a -> IO (v a)
toVector = toVectorSized dEFAULT_BUFSIZ
{-# INLINE toVector #-}
------------------------------------------------------------------------------
-- | Like 'toVector', but allows control over how large the vector buffer is to
-- start with.
toVectorSized :: Vector v a => Int -> InputStream a -> IO (v a)
toVectorSized n = toMutableVectorSized n >=> basicUnsafeFreezeCompat
{-# INLINE toVectorSized #-}
------------------------------------------------------------------------------
-- | Drains an 'InputStream', converting it to a mutable vector. Note that this
-- function reads the entire 'InputStream' strictly into memory and as such is
-- not recommended for streaming applications or where the size of the input is
-- not bounded or known.
toMutableVector :: VM.MVector v a => InputStream a -> IO (v (PrimState IO) a)
toMutableVector = toMutableVectorSized dEFAULT_BUFSIZ
------------------------------------------------------------------------------
-- | Like 'toMutableVector', but allows control over how large the vector
-- buffer is to start with.
toMutableVectorSized :: VM.MVector v a =>
Int -- ^ initial size of the vector buffer
-> InputStream a
-> IO (v (PrimState IO) a)
toMutableVectorSized initialSize input = vfNew initialSize >>= go
where
go vfi = S.read input >>= maybe (vfFinish vfi) (vfAppend vfi >=> go)
{-# INLINE toMutableVectorSized #-}
------------------------------------------------------------------------------
-- | 'vectorOutputStream' returns an 'OutputStream' which stores values fed
-- into it and an action which flushes all stored values to a vector.
--
-- The flush action resets the store.
--
-- Note that this function /will/ buffer any input sent to it on the heap.
-- Please don't use this unless you're sure that the amount of input provided
-- is bounded and will fit in memory without issues.
--
-- @
-- ghci> (os, flush) <- Streams.'vectorOutputStream' :: IO ('OutputStream' Int, IO (V.'Vector' Int))
-- ghci> Streams.'System.IO.Streams.write' (Just 1) os
-- ghci> Streams.'System.IO.Streams.write' (Just 2) os
-- ghci> flush
-- fromList [1,2]
-- ghci> Streams.'System.IO.Streams.write' (Just 3) os
-- ghci> Streams.'System.IO.Streams.write' Nothing os
-- ghci> Streams.'System.IO.Streams.write' (Just 4) os
-- ghci> flush
-- fromList [3]
-- @
vectorOutputStream :: Vector v c => IO (OutputStream c, IO (v c))
vectorOutputStream = vectorOutputStreamSized dEFAULT_BUFSIZ
{-# INLINE vectorOutputStream #-}
------------------------------------------------------------------------------
-- | Like 'vectorOutputStream', but allows control over how large the vector
-- buffer is to start with.
vectorOutputStreamSized :: Vector v c => Int -> IO (OutputStream c, IO (v c))
vectorOutputStreamSized n = do
(os, flush) <- mutableVectorOutputStreamSized n
return $! (os, flush >>= basicUnsafeFreezeCompat)
------------------------------------------------------------------------------
data VectorFillInfo v c = VectorFillInfo {
_vec :: !(v (PrimState IO) c)
, _idx :: {-# UNPACK #-} !(IORef Int)
-- TODO: vector contains its own size
, _sz :: {-# UNPACK #-} !(IORef Int)
}
------------------------------------------------------------------------------
vfNew :: MVector v a => Int -> IO (VectorFillInfo v a)
vfNew initialSize = do
v <- VM.unsafeNew initialSize
i <- newIORef 0
sz <- newIORef initialSize
return $! VectorFillInfo v i sz
------------------------------------------------------------------------------
vfFinish :: MVector v a =>
VectorFillInfo v a
-> IO (v (PrimState IO) a)
vfFinish vfi = liftM (flip VM.unsafeTake v) $ readIORef i
where
v = _vec vfi
i = _idx vfi
------------------------------------------------------------------------------
vfAppend :: MVector v a =>
VectorFillInfo v a
-> a
-> IO (VectorFillInfo v a)
vfAppend vfi !x = do
i <- readIORef iRef
sz <- readIORef szRef
if i < sz then add i else grow sz
where
v = _vec vfi
iRef = _idx vfi
szRef = _sz vfi
add i = do
VM.unsafeWrite v i x
writeIORef iRef $! i + 1
return vfi
grow sz = do
let !sz' = sz * 2
v' <- VM.unsafeGrow v sz
writeIORef szRef sz'
vfAppend (vfi { _vec = v' }) x
------------------------------------------------------------------------------
-- | 'mutableVectorOutputStream' returns an 'OutputStream' which stores values
-- fed into it and an action which flushes all stored values to a vector.
--
-- The flush action resets the store.
--
-- Note that this function /will/ buffer any input sent to it on the heap.
-- Please don't use this unless you're sure that the amount of input provided
-- is bounded and will fit in memory without issues.
mutableVectorOutputStream :: VM.MVector v c =>
IO (OutputStream c, IO (v (PrimState IO) c))
mutableVectorOutputStream = mutableVectorOutputStreamSized dEFAULT_BUFSIZ
------------------------------------------------------------------------------
-- | Like 'mutableVectorOutputStream', but allows control over how large the
-- vector buffer is to start with.
mutableVectorOutputStreamSized :: VM.MVector v c =>
Int
-> IO (OutputStream c, IO (v (PrimState IO) c))
mutableVectorOutputStreamSized initialSize = do
r <- vfNew initialSize >>= newMVar
c <- S.fromConsumer $ consumer r
return (c, flush r)
where
consumer r = go
where
go = S.await >>=
(maybe (return $! ()) $ \c -> do
liftIO $ modifyMVar_ r $ flip vfAppend c
go)
flush r = modifyMVar r $ \vfi -> do
!v <- vfFinish vfi
vfi' <- vfNew initialSize
return $! (vfi', v)
{-# INLINE mutableVectorOutputStreamSized #-}
------------------------------------------------------------------------------
-- | Given an IO action that requires an 'OutputStream', creates one and
-- captures all the output the action sends to it as a mutable vector.
--
-- Example:
--
-- @
-- ghci> import "Control.Applicative"
-- ghci> ('connect' \<\$\> 'System.IO.Streams.fromList' [1, 2, 3::'Int'])
-- \>\>= 'outputToMutableVector'
-- \>\>= V.'Data.Vector.freeze'
-- fromList [1,2,3]
-- @
outputToMutableVector :: MVector v a =>
(OutputStream a -> IO b)
-> IO (v (PrimState IO) a)
outputToMutableVector = outputToMutableVectorSized dEFAULT_BUFSIZ
{-# INLINE outputToMutableVector #-}
------------------------------------------------------------------------------
-- | Like 'outputToMutableVector', but allows control over how large the vector
-- buffer is to start with.
outputToMutableVectorSized :: MVector v a =>
Int
-> (OutputStream a -> IO b)
-> IO (v (PrimState IO) a)
outputToMutableVectorSized n f = do
(os, getVec) <- mutableVectorOutputStreamSized n
_ <- f os
getVec
{-# INLINE outputToMutableVectorSized #-}
------------------------------------------------------------------------------
-- | Given an IO action that requires an 'OutputStream', creates one and
-- captures all the output the action sends to it as a vector.
--
-- Example:
--
-- @
-- ghci> (('connect' <$> 'System.IO.Streams.fromList' [1, 2, 3]) >>= 'outputToVector')
-- :: IO ('Data.Vector.Vector' Int)
-- fromList [1,2,3]
-- @
outputToVector :: Vector v a => (OutputStream a -> IO b) -> IO (v a)
outputToVector = outputToVectorSized dEFAULT_BUFSIZ
{-# INLINE outputToVector #-}
------------------------------------------------------------------------------
-- | Like 'outputToVector', but allows control over how large the vector buffer
-- is to start with.
outputToVectorSized :: Vector v a =>
Int
-> (OutputStream a -> IO b)
-> IO (v a)
outputToVectorSized n = outputToMutableVectorSized n >=> basicUnsafeFreezeCompat
{-# INLINE outputToVectorSized #-}
------------------------------------------------------------------------------
-- | Splits an input stream into chunks of at most size @n@.
--
-- Example:
--
-- @
-- ghci> ('System.IO.Streams.fromList' [1..14::Int] >>= 'chunkVector' 4 >>= 'System.IO.Streams.toList')
-- :: IO ['Data.Vector.Vector' Int]
-- [fromList [1,2,3,4],fromList [5,6,7,8],fromList [9,10,11,12],fromList [13,14]]
-- @
chunkVector :: Vector v a => Int -> InputStream a -> IO (InputStream (v a))
chunkVector n input = if n <= 0
then error $ "chunkVector: bad size: " ++ show n
else vfNew n >>= fromGenerator . go n
where
doneChunk !vfi = do
liftIO (vfFinish vfi >>= V.unsafeFreeze) >>= yield
!vfi' <- liftIO $ vfNew n
go n vfi'
go !k !vfi | k <= 0 = doneChunk vfi
| otherwise = liftIO (S.read input) >>= maybe finish chunk
where
finish = do
v <- liftIO (vfFinish vfi >>= V.unsafeFreeze)
if V.null v then return $! () else yield v
chunk x = do
!vfi' <- liftIO $ vfAppend vfi x
go (k - 1) vfi'
{-# INLINE chunkVector #-}
------------------------------------------------------------------------------
-- | Feeds a vector to an 'OutputStream'. Does /not/ write an end-of-stream to
-- the stream.
--
-- @
-- ghci> let v = V.'fromList' [1..4] :: V.'Vector' Int
-- ghci> os \<- Streams.'unlines' Streams.'stdout' >>= Streams.'System.IO.Streams.contramap' (S.pack . show) :: IO ('OutputStream' Int)
-- ghci> Streams.'writeVector' v os
-- 1
-- 2
-- 3
-- 4
-- @
writeVector :: Vector v a => v a -> OutputStream a -> IO ()
writeVector v out = V.mapM_ (flip S.write out . Just) v
{-# INLINE writeVector #-}
------------------------------------------------------------------------------
dEFAULT_BUFSIZ :: Int
dEFAULT_BUFSIZ = 64