diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright John Ky (c) 2016
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Author name here nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,10 @@
+# hw-streams
+
+[![CircleCI](https://circleci.com/gh/haskell-works/hw-streams/tree/master.svg?style=svg)](https://circleci.com/gh/haskell-works/hw-streams/tree/master)
+[![Travis](https://travis-ci.org/haskell-works/hw-streams.svg?branch=master)](https://travis-ci.org/haskell-works/hw-streams)
+
+Pure streaming library.
+
+This is a reproduction of the work in [Exploiting Vector Instructions with Generalized Stream Fusion][1]
+
+[1]: https://www.microsoft.com/en-us/research/wp-content/uploads/2016/07/haskell-beats-C.pdf
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/bench/Baseline/Data/Streams/Vector/Storable.hs b/bench/Baseline/Data/Streams/Vector/Storable.hs
new file mode 100644
--- /dev/null
+++ b/bench/Baseline/Data/Streams/Vector/Storable.hs
@@ -0,0 +1,9 @@
+module Baseline.Data.Streams.Vector.Storable where
+
+import qualified Data.Vector.Storable                      as DVS
+import qualified HaskellWorks.Data.Streams.Vector.Storable as DVS
+
+-- | Version of map that does not do fusion
+map :: (DVS.Storable a, DVS.Storable b) => (a -> b) -> DVS.Vector a -> DVS.Vector b
+map f = DVS.unstream . fmap f . DVS.stream
+{-# INLINE map #-}
diff --git a/bench/Main.hs b/bench/Main.hs
new file mode 100644
--- /dev/null
+++ b/bench/Main.hs
@@ -0,0 +1,63 @@
+{-# LANGUAGE BangPatterns        #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+module Main where
+
+import Control.Monad  (join)
+import Criterion.Main
+import Data.Word
+
+import qualified Baseline.Data.Streams.Vector.Storable     as USDVS
+import qualified Data.Vector.Storable                      as DVS
+import qualified HaskellWorks.Data.Streams.Stream          as S
+import qualified HaskellWorks.Data.Streams.Vector.Storable as SDVS
+import qualified Reference.Data.Streams.Vector.Storable    as DVS
+
+mkReplicateVector :: IO (DVS.Vector Word64)
+mkReplicateVector = do
+  let !v = DVS.replicate 100000 (0 :: Word64)
+  return v
+
+mkEnumVector :: IO (DVS.Vector Word64)
+mkEnumVector = do
+  let !v = SDVS.enumFromStepN 0 1 100000
+  return v
+
+mkEnumVector2 :: IO (DVS.Vector Word64, DVS.Vector Word64)
+mkEnumVector2 = do
+  let !v = SDVS.enumFromStepN 0 1 100000
+  let !w = SDVS.enumFromStepN 0 1 100000
+  return (v, w)
+
+sumall :: DVS.Vector Word64 -> Word64
+sumall = S.foldl (+) 123 . SDVS.stream . SDVS.map (+1) . SDVS.map (+1) . SDVS.map (+1)
+
+runMap :: DVS.Vector Word64 -> DVS.Vector Word64
+runMap = SDVS.map (+123) . SDVS.map (+123) . SDVS.map (+123)
+
+runMapUnfused :: DVS.Vector Word64 -> DVS.Vector Word64
+runMapUnfused = USDVS.map (+123) . USDVS.map (+123) . USDVS.map (+123)
+
+benchThings :: [Benchmark]
+benchThings = join $
+  [ [ env mkReplicateVector $ \v -> bgroup "mkReplicateVector"
+      [ bench "DVS.map"         (whnf runMap              v)
+      , bench "DVS.mapUnfused"  (whnf runMapUnfused       v)
+      ]
+    ]
+  , [ env mkEnumVector $ \v -> bgroup "mkEnumVector"
+      [ bench "DVS.map"         (whnf runMap              v)
+      , bench "DVS.mapUnfused"  (whnf runMapUnfused       v)
+      , bench "DVS.sumall"      (whnf sumall              v)
+      , bench "DVS.foldl"       (whnf (SDVS.foldl (+) 0)  v)
+      ]
+    ]
+  , [ env mkEnumVector2 $ \ ~(v, w) -> bgroup "mkEnumVector2"
+      [ bench "SDVS.dotp"        (whnf (SDVS.dotp v)       w)
+      , bench "DVS.dotp"         (whnf (DVS.dotp v)       w)
+      ]
+    ]
+  ]
+
+main :: IO ()
+main = defaultMain benchThings
diff --git a/bench/Reference/Data/Streams/Vector/Storable.hs b/bench/Reference/Data/Streams/Vector/Storable.hs
new file mode 100644
--- /dev/null
+++ b/bench/Reference/Data/Streams/Vector/Storable.hs
@@ -0,0 +1,8 @@
+module Reference.Data.Streams.Vector.Storable where
+
+import Data.Vector.Storable (Storable)
+
+import qualified Data.Vector.Storable as DVS
+
+dotp :: (Storable a, Num a) => DVS.Vector a -> DVS.Vector a -> a
+dotp v w = DVS.sum (DVS.zipWith (*) v w)
diff --git a/hw-streams.cabal b/hw-streams.cabal
new file mode 100644
--- /dev/null
+++ b/hw-streams.cabal
@@ -0,0 +1,123 @@
+-- This file has been generated from package.yaml by hpack version 0.18.1.
+--
+-- see: https://github.com/sol/hpack
+
+name:           hw-streams
+version:        0.0.0.1
+synopsis:       Primitive functions and data types
+description:    Primitive functions and data types.
+category:       Data
+stability:      Experimental
+homepage:       http://github.com/haskell-works/hw-streams#readme
+bug-reports:    https://github.com/haskell-works/hw-streams/issues
+author:         John Ky
+maintainer:     newhoggy@gmail.com
+copyright:      2016-2018 John Ky
+license:        BSD3
+license-file:   LICENSE
+tested-with:    GHC == 8.4.2, GHC == 8.2.2, GHC == 8.0.2, GHC == 7.10.3
+build-type:     Simple
+cabal-version:  >= 1.10
+
+extra-source-files:
+    README.md
+
+source-repository head
+  type: git
+  location: https://github.com/haskell-works/hw-streams
+
+flag bounds-checking-enabled
+  description: Enable bmi2 instruction set
+  manual: False
+  default: False
+
+library
+  hs-source-dirs:
+      src
+  other-extensions: AllowAmbiguousTypes
+  ghc-options: -Wall -O2 -msse4.2
+  build-depends:
+      base            >= 4.8        && < 5
+    , bytestring      >= 0.9        && < 0.11
+    , ghc-prim        >= 0.5.1.1    && < 0.6
+    , hw-bits         >= 0.7.0.3    && < 0.8
+    , hw-prim         >= 0.6.2.17   && < 0.7
+    , mmap            >= 0.5        && < 0.6
+    , primitive       >= 0.6.3.0    && < 0.7
+    , vector          >= 0.12       && < 0.13
+    , semigroups      >= 0.8.4      && < 0.19
+    , transformers    >= 0.4        && < 0.6
+  if flag(bounds-checking-enabled)
+    cpp-options: -DBOUNDS_CHECKING_ENABLED
+  exposed-modules:
+      HaskellWorks.Data.Streams.Internal
+      HaskellWorks.Data.Streams.Internal.Bundle
+      HaskellWorks.Data.Streams.Internal.Chunk
+      HaskellWorks.Data.Streams.Size
+      HaskellWorks.Data.Streams.Stream
+      HaskellWorks.Data.Streams.Stream.Ops
+      HaskellWorks.Data.Streams.Vector
+      HaskellWorks.Data.Streams.Vector.Storable
+  other-modules:
+      Paths_hw_streams
+  default-language: Haskell2010
+
+test-suite hw-streams-test
+  type: exitcode-stdio-1.0
+  main-is: Spec.hs
+  hs-source-dirs:
+      test
+  other-extensions: AllowAmbiguousTypes
+  ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N
+  build-depends:
+      base            >= 4.8        && < 5
+    , bytestring      >= 0.9        && < 0.11
+    , ghc-prim        >= 0.5.1.1    && < 0.6
+    , hw-bits         >= 0.7.0.3    && < 0.8
+    , hw-prim         >= 0.6.2.17   && < 0.7
+    , mmap            >= 0.5        && < 0.6
+    , primitive       >= 0.6.3.0    && < 0.7
+    , vector          >= 0.12       && < 0.13
+    , semigroups      >= 0.8.4      && < 0.19
+    , transformers    >= 0.4        && < 0.6
+    , QuickCheck          >= 2.10       && < 2.12
+    , directory           >= 1.2        && < 1.4
+    , exceptions          >= 0.8        && < 0.11
+    , hedgehog            >= 0.5        && < 0.7
+    , hspec               >= 2.4        && < 2.6
+    , hw-hspec-hedgehog   >= 0.1        && < 0.2
+    , hw-streams
+  if flag(bounds-checking-enabled)
+    cpp-options: -DBOUNDS_CHECKING_ENABLED
+  other-modules:
+      HaskellWorks.Data.Stream.Vector.StorableSpec
+      Test.Gen
+  default-language: Haskell2010
+
+benchmark bench
+  type: exitcode-stdio-1.0
+  main-is: Main.hs
+  hs-source-dirs:
+      bench
+  other-extensions: AllowAmbiguousTypes
+  ghc-options: -Wall -O2 -msse4.2
+  build-depends:
+      base            >= 4.8        && < 5
+    , bytestring      >= 0.9        && < 0.11
+    , ghc-prim        >= 0.5.1.1    && < 0.6
+    , hw-bits         >= 0.7.0.3    && < 0.8
+    , hw-prim         >= 0.6.2.17   && < 0.7
+    , mmap            >= 0.5        && < 0.6
+    , primitive       >= 0.6.3.0    && < 0.7
+    , vector          >= 0.12       && < 0.13
+    , semigroups      >= 0.8.4      && < 0.19
+    , transformers    >= 0.4        && < 0.6
+    , criterion     >= 1.2      && < 1.6
+    , hw-streams
+    , mmap          >= 0.5      && < 0.6
+  if flag(bounds-checking-enabled)
+    cpp-options: -DBOUNDS_CHECKING_ENABLED
+  other-modules:
+      Baseline.Data.Streams.Vector.Storable
+      Reference.Data.Streams.Vector.Storable
+  default-language: Haskell2010
diff --git a/src/HaskellWorks/Data/Streams/Internal.hs b/src/HaskellWorks/Data/Streams/Internal.hs
new file mode 100644
--- /dev/null
+++ b/src/HaskellWorks/Data/Streams/Internal.hs
@@ -0,0 +1,5 @@
+module HaskellWorks.Data.Streams.Internal where
+
+inplace :: a -> a
+inplace = id
+{-# INLINE [1] inplace #-}
diff --git a/src/HaskellWorks/Data/Streams/Internal/Bundle.hs b/src/HaskellWorks/Data/Streams/Internal/Bundle.hs
new file mode 100644
--- /dev/null
+++ b/src/HaskellWorks/Data/Streams/Internal/Bundle.hs
@@ -0,0 +1,13 @@
+module HaskellWorks.Data.Streams.Internal.Bundle where
+
+import HaskellWorks.Data.Streams.Internal.Chunk (Chunk)
+
+import qualified HaskellWorks.Data.Streams.Stream as S
+
+type Size = Int
+
+data Bundle a = Bundle
+  { bundleSize   :: Size
+  , bundleElems  :: S.Stream a
+  , bundleChunks :: S.Stream (Chunk a)
+  }
diff --git a/src/HaskellWorks/Data/Streams/Internal/Chunk.hs b/src/HaskellWorks/Data/Streams/Internal/Chunk.hs
new file mode 100644
--- /dev/null
+++ b/src/HaskellWorks/Data/Streams/Internal/Chunk.hs
@@ -0,0 +1,12 @@
+{-# LANGUAGE RankNTypes #-}
+
+module HaskellWorks.Data.Streams.Internal.Chunk where
+
+import Control.Monad.Primitive
+
+import qualified Data.Vector.Storable as DVS
+
+data Chunk a = Chunk
+  { chunkSize  :: Int
+  , chunkWrite :: forall m. PrimMonad m => DVS.MVector (PrimState m) a -> m ()
+  }
diff --git a/src/HaskellWorks/Data/Streams/Size.hs b/src/HaskellWorks/Data/Streams/Size.hs
new file mode 100644
--- /dev/null
+++ b/src/HaskellWorks/Data/Streams/Size.hs
@@ -0,0 +1,7 @@
+module HaskellWorks.Data.Streams.Size
+  ( Size(..)
+  , smaller
+  , larger
+  ) where
+
+import Data.Vector.Fusion.Bundle.Size
diff --git a/src/HaskellWorks/Data/Streams/Stream.hs b/src/HaskellWorks/Data/Streams/Stream.hs
new file mode 100644
--- /dev/null
+++ b/src/HaskellWorks/Data/Streams/Stream.hs
@@ -0,0 +1,103 @@
+{-# LANGUAGE CPP   #-}
+{-# LANGUAGE GADTs #-}
+
+module HaskellWorks.Data.Streams.Stream where
+
+import Data.Bool
+import HaskellWorks.Data.Streams.Size
+
+import Prelude hiding (drop, zipWith)
+
+data Stream a where
+  Stream :: ()
+    => (s -> Step s a)
+    -> s
+    -> Size
+    -> Stream a
+
+instance Functor Stream where
+  fmap f (Stream step s i) = Stream (fmap f <$> step) s i
+  {-# INLINE fmap #-}
+
+data Step s a
+  = Yield a s
+  | Skip s
+  | Done
+
+instance Functor (Step s) where
+  fmap f (Yield a s) = Yield (f a) s
+  fmap _ (Skip s)    = Skip s
+  fmap _ Done        = Done
+  {-# INLINE fmap #-}
+
+zipWith :: (a -> b -> c) -> Stream a -> Stream b -> Stream c
+zipWith f (Stream stepa sa na) (Stream stepb sb nb) = Stream step (sa, sb, Nothing) (smaller na nb)
+  where step (ta, tb, Nothing) = case stepa ta of
+          Yield xa ta0 -> Skip (ta0, tb, Just xa)
+          Skip ta0     -> Skip (ta0, tb, Nothing)
+          Done         -> Done
+        step (ta, tb, Just xa) = case stepb tb of
+          Yield y tb0 -> Yield (f xa y) (ta, tb0, Nothing)
+          Skip tb0    -> Skip (ta, tb0, Just xa)
+          Done        -> Done
+        {-# INLINE [0] step #-}
+{-# INLINE [1] zipWith #-}
+
+zipWithState :: (a -> b -> s -> (c, s)) -> s -> Stream a -> Stream b -> Stream c
+zipWithState f state (Stream stepa sa na) (Stream stepb sb nb) = Stream step (sa, sb, Nothing, state) (smaller na nb)
+  where step (ta, tb, Nothing, oldState) = case stepa ta of
+          Yield xa ta0 -> Skip (ta0, tb, Just xa, oldState)
+          Skip ta0     -> Skip (ta0, tb, Nothing, oldState)
+          Done         -> Done
+        step (ta, tb, Just xa, oldState) = case stepb tb of
+          Yield y tb0 -> let (newValue, newState) = f xa y state in Yield newValue (ta, tb0, Nothing, newState)
+          Skip tb0    -> Skip (ta, tb0, Just xa, oldState)
+          Done        -> Done
+        {-# INLINE [0] step #-}
+{-# INLINE [1] zipWithState #-}
+
+enumFromStepN :: Num a => a -> a -> Int -> Stream a
+enumFromStepN x y n = x `seq` y `seq` n `seq` Stream step (x, n) (Exact n)
+  where step (w, m) | m > 0     = Yield w (w + y, m - 1)
+                    | otherwise = Done
+        {-# INLINE [0] step #-}
+{-# INLINE [1] enumFromStepN #-}
+
+foldl :: (a -> b -> a) -> a -> Stream b -> a
+foldl f z (Stream step s _) = loop z s
+  where loop za sa = za `seq` case step sa of
+          Yield x sb -> loop (f za x) sb
+          Skip sb    -> loop za sb
+          Done       -> za
+{-# INLINE [1] foldl #-}
+
+drop :: Int -> Stream a -> Stream a
+drop n s@(Stream step state size) = if n > 0
+  then case step state of
+    Yield _ newState -> drop (n - 1)  $ Stream step newState (size - 1)
+    Skip newState    -> drop  n       $ Stream step newState size
+    Done             -> s
+  else s
+
+{-# RULES
+  "zipWith xs xs [Vector.Stream]" forall f xs. zipWith f xs xs = fmap (\x -> f x x) xs   #-}
+
+append :: Stream a -> Stream a -> Stream a
+append (Stream stepa ta na) (Stream stepb tb nb) = Stream step (Left ta) (na + nb)
+  where step (Left sa) = case stepa sa of
+          Yield x sa' -> Yield x (Left  sa')
+          Skip    sa' -> Skip    (Left  sa')
+          Done        -> Skip    (Right tb)
+        step (Right sb) = case stepb sb of
+          Yield x sb' -> Yield x (Right sb')
+          Skip    sb' -> Skip    (Right sb')
+          Done        -> Done
+        {-# INLINE step #-}
+{-# INLINE append #-}
+
+singleton :: a -> Stream a
+singleton a = Stream (bool Done (Yield a False)) True 1
+
+repeat :: Int -> a -> Stream a
+repeat n a = Stream step n 1
+  where step i = if i > 0 then Yield a (i - 1) else Done
diff --git a/src/HaskellWorks/Data/Streams/Stream/Ops.hs b/src/HaskellWorks/Data/Streams/Stream/Ops.hs
new file mode 100644
--- /dev/null
+++ b/src/HaskellWorks/Data/Streams/Stream/Ops.hs
@@ -0,0 +1,41 @@
+{-# LANGUAGE MagicHash #-}
+
+module HaskellWorks.Data.Streams.Stream.Ops where
+
+import Data.Word
+import GHC.Int
+import GHC.Prim
+import GHC.Word                         hiding (ltWord)
+import HaskellWorks.Data.Bits.BitWise   ((.&.), (.<.), (.>.), (.^.), (.|.))
+import HaskellWorks.Data.Positioning
+import HaskellWorks.Data.Streams.Stream
+
+import qualified HaskellWorks.Data.Bits.BitWise   as BW
+import qualified HaskellWorks.Data.Streams.Stream as HW
+
+ltWord :: Word64 -> Word64 -> Word64
+ltWord (W64# a#) (W64# b#) = fromIntegral (I64# (ltWord# a# b#))
+{-# INLINE ltWord #-}
+
+comp :: Stream Word64 -> Stream Word64
+comp = fmap BW.comp
+
+bitwiseAnd :: Stream Word64 -> Stream Word64 -> Stream Word64
+bitwiseAnd = HW.zipWith (.&.)
+
+bitwiseOr :: Stream Word64 -> Stream Word64 -> Stream Word64
+bitwiseOr = HW.zipWith (.|.)
+
+bitwiseXor :: Stream Word64 -> Stream Word64 -> Stream Word64
+bitwiseXor = HW.zipWith (.^.)
+
+bitwiseShiftDown :: Count -> Stream Word64 -> Stream Word64
+bitwiseShiftDown n as = HW.zipWith splice bs (HW.drop 1 bs `append` HW.singleton 0)
+  where bs = HW.drop i as `append` HW.repeat i 0
+        o = n `mod` 64
+        i = fromIntegral (n `div` 64)
+        splice :: Word64 -> Word64 -> Word64
+        splice a b = (a .>. o) .|. (b .<. (64 - o))
+
+add :: Stream Word64 -> Stream Word64 -> Stream Word64
+add = HW.zipWithState (\a b c -> let d = a + b in (c + d, d `ltWord` a)) 0
diff --git a/src/HaskellWorks/Data/Streams/Vector.hs b/src/HaskellWorks/Data/Streams/Vector.hs
new file mode 100644
--- /dev/null
+++ b/src/HaskellWorks/Data/Streams/Vector.hs
@@ -0,0 +1,46 @@
+{-# LANGUAGE ScopedTypeVariables #-}
+
+module HaskellWorks.Data.Streams.Vector where
+
+import Control.Monad
+import Control.Monad.ST
+import HaskellWorks.Data.Streams.Internal
+import HaskellWorks.Data.Streams.Size
+import HaskellWorks.Data.Streams.Stream
+
+import qualified Data.Vector                      as DV
+import qualified Data.Vector.Mutable              as DVM
+import qualified HaskellWorks.Data.Streams.Stream as HW
+
+unstream :: forall a. HW.Stream a -> DV.Vector a
+unstream (HW.Stream step initialState size) = runST $ do
+  v <- case size of
+    Exact n -> DVM.unsafeNew n
+    Max   n -> DVM.unsafeNew n
+    Unknown -> DVM.unsafeNew (32 * 1024)
+  loop step v 0 initialState
+  where loop :: (s -> Step s a) -> DVM.MVector t a -> Int -> s -> ST t (DV.Vector a)
+        loop g v i s = case g s of
+            Yield a s' -> do
+              when (i >= DVM.length v) $ void $ DVM.unsafeGrow v (i * 2)
+              DVM.unsafeWrite v i a
+              loop g v (i + 1) s'
+            Skip s0 -> loop g v i s0
+            Done -> DV.freeze v
+{-# INLINE [1] unstream #-}
+
+stream :: forall a. DV.Vector a -> Stream a
+stream v = Stream step 0 (Exact len)
+  where len = DV.length v
+        step i = if i >= len
+          then Done
+          else Yield (DV.unsafeIndex v i) (i + 1)
+{-# INLINE [1] stream #-}
+
+map :: (a -> b) -> DV.Vector a -> DV.Vector b
+map f = unstream . inplace (fmap f) . stream
+{-# INLINE map #-}
+
+{-# RULES
+  "stream/unstream" forall f. stream (unstream f) = f
+  #-}
diff --git a/src/HaskellWorks/Data/Streams/Vector/Storable.hs b/src/HaskellWorks/Data/Streams/Vector/Storable.hs
new file mode 100644
--- /dev/null
+++ b/src/HaskellWorks/Data/Streams/Vector/Storable.hs
@@ -0,0 +1,84 @@
+{-# LANGUAGE ScopedTypeVariables #-}
+
+module HaskellWorks.Data.Streams.Vector.Storable
+  ( stream
+  , unstream
+
+  , map
+  , zipWith
+
+  , enumFromStepN
+  , foldl
+
+  , dotp
+  , sum
+  ) where
+
+import Control.Monad
+import Control.Monad.ST
+import Data.Vector.Storable               (Storable)
+import HaskellWorks.Data.Streams.Internal (inplace)
+import HaskellWorks.Data.Streams.Size
+import HaskellWorks.Data.Streams.Stream   (Step (..), Stream (..))
+import Prelude                            hiding (foldl, map, sum, zipWith)
+
+import qualified Data.Vector.Storable             as DVS
+import qualified Data.Vector.Storable.Mutable     as DVSM
+import qualified HaskellWorks.Data.Streams.Stream as S
+
+unstream :: forall a. Storable a => S.Stream a -> DVS.Vector a
+unstream (S.Stream step initialState size) = runST $ do
+  v <- case size of
+    Exact n -> DVSM.unsafeNew n
+    Max   n -> DVSM.unsafeNew n
+    Unknown -> DVSM.unsafeNew (32 * 1024)
+  loop step v 0 initialState
+  where loop :: (s -> Step s a) -> DVSM.MVector t a -> Int -> s -> ST t (DVS.Vector a)
+        loop g v i s = case g s of
+            Yield a s' -> do
+              when (i >= DVSM.length v) $ void $ DVSM.unsafeGrow v (i * 2)
+              DVSM.unsafeWrite v i a
+              loop g v (i + 1) s'
+            Skip s0 -> loop g v i s0
+            Done -> DVS.freeze v
+{-# INLINE [1] unstream #-}
+
+stream :: forall a. Storable a => DVS.Vector a -> Stream a
+stream v = Stream step 0 (Exact len)
+  where len = DVS.length v
+        step i = if i >= len
+          then Done
+          else Yield (DVS.unsafeIndex v i) (i + 1)
+{-# INLINE [1] stream #-}
+
+map :: (Storable a, Storable b)
+  => (a -> b)
+  -> DVS.Vector a
+  -> DVS.Vector b
+map f = unstream . inplace (fmap f) . stream
+{-# INLINE map #-}
+
+zipWith :: (Storable a, Storable b, Storable c)
+  => (a -> b -> c)
+  -> DVS.Vector a
+  -> DVS.Vector b
+  -> DVS.Vector c
+zipWith f v w = unstream (S.zipWith f (stream v) (stream w))
+
+enumFromStepN :: (Num a, Storable a) => a -> a -> Int -> DVS.Vector a
+enumFromStepN x y = unstream . inplace (S.enumFromStepN x y)
+{-# INLINE [1] enumFromStepN #-}
+
+foldl :: Storable b => (a -> b -> a) -> a -> DVS.Vector b -> a
+foldl f z = inplace (S.foldl f z) . stream
+{-# INLINE [1] foldl #-}
+
+sum :: (Storable a, Num a) => DVS.Vector a -> a
+sum = foldl (+) 0
+
+dotp :: (Storable a, Num a) => DVS.Vector a -> DVS.Vector a -> a
+dotp v w = sum (zipWith (*) v w)
+
+{-# RULES
+  "stream/unstream" forall f. stream (unstream f) = f
+  #-}
diff --git a/test/HaskellWorks/Data/Stream/Vector/StorableSpec.hs b/test/HaskellWorks/Data/Stream/Vector/StorableSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/HaskellWorks/Data/Stream/Vector/StorableSpec.hs
@@ -0,0 +1,33 @@
+{-# LANGUAGE OverloadedStrings   #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+module HaskellWorks.Data.Stream.Vector.StorableSpec
+  ( spec
+  ) where
+
+import HaskellWorks.Hspec.Hedgehog
+import Hedgehog
+import Test.Hspec
+
+import qualified Data.Vector.Storable                      as DVS
+import qualified HaskellWorks.Data.Streams.Vector.Storable as SDVS
+import qualified Hedgehog.Gen                              as G
+import qualified Hedgehog.Range                            as R
+import qualified Test.Gen                                  as G
+
+{-# ANN module ("HLint: Ignore Redundant do" :: String) #-}
+
+spec :: Spec
+spec = describe "HaskellWorks.Data.Stream.Vector.StorableSpec" $ do
+  it "map" $ requireProperty $ do
+    v <- forAll $ G.vector (R.linear 0 100) (G.word64 (R.constantBounded))
+
+    DVS.map (+ 1) v === SDVS.map (+ 1) v
+  it "foldl" $ requireProperty $ do
+    v <- forAll $ G.vector (R.linear 0 100) (G.word64 (R.constantBounded))
+
+    DVS.foldl (+) 0 v === SDVS.foldl (+) 0 v
+  it "sum" $ requireProperty $ do
+    v <- forAll $ G.vector (R.linear 0 100) (G.word64 (R.constantBounded))
+
+    DVS.sum v === SDVS.sum v
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec.hs
@@ -0,0 +1,1 @@
+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
diff --git a/test/Test/Gen.hs b/test/Test/Gen.hs
new file mode 100644
--- /dev/null
+++ b/test/Test/Gen.hs
@@ -0,0 +1,12 @@
+module Test.Gen where
+
+import Data.Vector.Storable        (Storable)
+import HaskellWorks.Hspec.Hedgehog
+import Hedgehog
+import Test.Hspec
+
+import qualified Data.Vector.Storable as DVS
+import qualified Hedgehog.Gen         as G
+
+vector :: (MonadGen m, Storable a) => Range Int -> m a -> m (DVS.Vector a)
+vector r g = DVS.fromList <$> G.list r g
