diff --git a/shh.cabal b/shh.cabal
--- a/shh.cabal
+++ b/shh.cabal
@@ -1,5 +1,5 @@
 name:                shh
-version:             0.2.0.1
+version:             0.2.0.2
 synopsis:            Simple shell scripting from Haskell
 description:         Provides a shell scripting environment for Haskell. It
                      helps you all external binaries, and allows you to
diff --git a/src/Shh.hs b/src/Shh.hs
--- a/src/Shh.hs
+++ b/src/Shh.hs
@@ -10,10 +10,8 @@
     , Proc()
     -- | == Piping and Redirection
     , PipeResult(..)
-    , (<|)
     , Stream(..)
     , devNull
-    -- | == Reading @stdout@
     -- === Lazy/Streaming reads
     -- These reads are lazy. The process is run long enough to produce
     -- the amount of output that is actually used. It is therefor suitable
@@ -21,7 +19,6 @@
     -- as soon the function finishes. Note that the result is forced to
     -- normal form to prevent any accidental reading after the process has
     -- terminated.
-    , withRead
     , withReadSplit0
     , withReadLines
     , withReadWords
@@ -32,16 +29,15 @@
     , readLines
     , readWords
     , readAuto
+    -- | === Writing to @stdin@
+    , (<<<), (>>>)
+    , readWriteProc
+    , apply
     -- | === String manipulation
     -- Utility functions for dealing with common string issues in shell
     -- scripting.
     , trim
     , split0
-    -- | == Writing to @stdin@
-    , writeProc
-    , (<<<), (>>>)
-    , readWriteProc
-    , apply
     -- | == Exceptions
     -- If any exception is allowed to propagate out of a pipeline, all the
     -- processes comprising the pipeline will be terminated. This is contrary
diff --git a/src/Shh/Internal.hs b/src/Shh/Internal.hs
--- a/src/Shh/Internal.hs
+++ b/src/Shh/Internal.hs
@@ -1,4 +1,6 @@
 {-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE KindSignatures #-}
+{-# LANGUAGE DataKinds #-}
 {-# LANGUAGE DefaultSignatures #-}
 {-# LANGUAGE DeriveFunctor #-}
 {-# LANGUAGE LambdaCase #-}
@@ -13,7 +15,7 @@
 
 
 import Control.Concurrent.Async
-import Control.DeepSeq (rnf,force,NFData)
+import Control.DeepSeq (force,NFData)
 import Control.Exception as C
 import Control.Monad
 import Control.Monad.IO.Class
@@ -75,6 +77,10 @@
     --       1       1       6
     (|>) :: Proc a -> Proc a -> f a
 
+    -- | Flipped version of `|>`
+    (<|) :: Proc a -> Proc a -> f a
+    (<|) = flip (|>)
+
     -- | Similar to `|!>` except that it connects stderr to stdin of the
     -- next process in the chain.
     --
@@ -96,15 +102,20 @@
     -- > ls &!> StdOut
     (&!>) :: Proc a -> Stream -> f a
 
--- | Flipped version of `|>`
-(<|) :: PipeResult f => Proc a -> Proc a -> f a
-(<|) = flip (|>)
+    -- | Provide the stdin of a `Proc` from a `String`
+    writeProc :: Proc a -> String -> f a
 
+    -- | Run a process and capture it's output lazily. Once the continuation
+    -- is completed, the handles are closed, and the process is terminated.
+    withRead :: (NFData b) => Proc a -> (String -> IO b) -> f b
+
 instance PipeResult IO where
     a |> b = runProc $ a |> b
     a |!> b = runProc $ a |!> b
     a &> s = runProc $ a &> s
     a &!> s = runProc $ a &!> s
+    writeProc p s = runProc $ writeProc p s
+    withRead p k = runProc $ withRead p k
 
 -- | Create a pipe, and close both ends on exception.
 withPipe :: (Handle -> Handle -> IO a) -> IO a
@@ -144,8 +155,18 @@
         withBinaryFile path WriteMode $ \h -> f i o h pl pw
     (Proc f) &!> (Append path) = Proc $ \i o _ pl pw ->
         withBinaryFile path AppendMode $ \h -> f i o h pl pw
-    
 
+    writeProc (Proc f) input = Proc $ \_ o e pl pw -> do
+        withPipe $ \r w ->
+            fst <$> concurrently
+                (f r o e pl (pw `finally` hClose r))
+                (hPutStr w input `finally` hClose w)
+
+    withRead (Proc f) k = Proc $ \i _ e pl pw -> do
+        withPipe $ \r w -> do
+            withAsync (f i w e pl (hClose w `finally` pw)) $ \_ ->
+                (hGetContents r >>= k >>= C.evaluate . force) `finally` hClose r
+
 -- | Type used to represent destinations for redirects. @`Truncate` file@
 -- is like @> file@ in a shell, and @`Append` file@ is like @>> file@.
 data Stream = StdOut | StdErr | Truncate FilePath | Append FilePath
@@ -214,54 +235,36 @@
             (waitProc cmd args ph `onException` terminateProcess ph) `finally` pw
 
 
--- | Read the stdout of a `Proc` in any `MonadIO` (including other `Proc`s).
+-- | Read the stdout of a `Proc`. This captures stdout, so further piping will
+-- not see anything on the input.
+--
 -- This is strict, so the whole output is read into a `String`. See `withRead`
 -- for a lazy version that can be used for streaming.
-readProc :: MonadIO io => Proc a -> io String
+readProc :: PipeResult io => Proc a -> io String
 readProc p = withRead p pure
 
--- | Run a process and capture it's output lazily. Once the continuation
--- is completed, the handles are closed, and the process is terminated.
-withRead :: (NFData b, MonadIO io) => Proc a -> (String -> IO b) -> io b
-withRead (Proc f) k = liftIO $ 
-    withPipe $ \r w -> do
-        withAsync (f stdin w stderr (pure ()) (hClose w)) $ \_ ->
-            (hGetContents r >>= k >>= C.evaluate . force) `finally` hClose r
-
 -- | Apply a transformation function to the string before the IO action.
-withRead' :: (NFData b, MonadIO io) => (String -> a) -> Proc x -> (a -> IO b) -> io b
+withRead' :: (NFData b, PipeResult io) => (String -> a) -> Proc x -> (a -> IO b) -> io b
 withRead' f p io = withRead p (io . f)
 
 -- | Like @'withRead'@ except it splits the string with @'split0'@ first.
-withReadSplit0 :: (NFData b, MonadIO io) => Proc a -> ([String] -> IO b) -> io b
+withReadSplit0 :: (NFData b, PipeResult io) => Proc a -> ([String] -> IO b) -> io b
 withReadSplit0 = withRead' split0
 
 -- | Like @'withRead'@ except it splits the string with @'lines'@ first.
 --
 -- NB: Please consider using @'withReadSplit0'@ where you can.
-withReadLines :: (NFData b, MonadIO io) => Proc a -> ([String] -> IO b) -> io b
+withReadLines :: (NFData b, PipeResult io) => Proc a -> ([String] -> IO b) -> io b
 withReadLines = withRead' lines
 
 -- | Like @'withRead'@ except it splits the string with @'words'@ first.
-withReadWords :: (NFData b, MonadIO io) => Proc a -> ([String] -> IO b) -> io b
+withReadWords :: (NFData b, PipeResult io) => Proc a -> ([String] -> IO b) -> io b
 withReadWords = withRead' words
 
--- | Read and write to a `Proc`...
+-- | Read and write to a `Proc`. Same as
+-- @readProc proc <<< input@
 readWriteProc :: MonadIO io => Proc a -> String -> io String
-readWriteProc (Proc f) input = liftIO $ do
-    (ri,wi) <- createPipe
-    (ro,wo) <- createPipe
-    (_,o) <- concurrently
-        (concurrently
-            (f ri wo stderr (pure ()) (hClose wo `finally` hClose ri))
-            (hPutStr wi input `finally` hClose wi)
-        ) (do
-            output <- hGetContents ro
-            C.evaluate $ rnf output
-            hClose ro
-            pure output
-        )
-    pure o
+readWriteProc p input = liftIO $ readProc p <<< input
 
 -- | Some as `readWriteProc`. Apply a `Proc` to a `String`.
 --
@@ -270,21 +273,13 @@
 apply :: MonadIO io => Proc a -> String -> io String
 apply = readWriteProc
 
--- | Provide the stdin of a `Proc` from a `String`
-writeProc :: MonadIO io => Proc a -> String -> io a
-writeProc (Proc f) input = liftIO $ do
-    (r,w) <- createPipe
-    fst <$> concurrently
-        (f r stdout stderr (pure ()) (hClose r))
-        (hPutStr w input `finally` hClose w)
-
 -- | Flipped, infix version of `writeProc`
-(>>>) :: MonadIO io => String -> Proc a -> io a
+(>>>) :: PipeResult io => String -> Proc a -> io a
 (>>>) = flip writeProc
 
 
 -- | Infix version of `writeProc`
-(<<<) :: MonadIO io => Proc a -> String -> io a
+(<<<) :: PipeResult io => Proc a -> String -> io a
 (<<<) = writeProc
 
 -- | What on a given `ProcessHandle`, and throw an exception of
@@ -333,7 +328,7 @@
         getCode (Left  f) = failureCode f
 
 -- | Like `readProc`, but trim leading and tailing whitespace.
-readTrim :: MonadIO io => Proc a -> io String
+readTrim :: (Functor io, PipeResult io) => Proc a -> io String
 readTrim = fmap trim . readProc
 
 -- | A class for things that can be converted to arguments on the command
diff --git a/test/Test.hs b/test/Test.hs
--- a/test/Test.hs
+++ b/test/Test.hs
@@ -7,7 +7,7 @@
 import Test.Tasty.HUnit
 import Test.Tasty.QuickCheck
 
-$(loadEnv SearchPath)
+$(load SearchPath ["shasum", "xxd", "echo", "cat", "false", "mktemp", "sleep", "rm"])
 
 main = do
     putStrLn "################################################"
