diff --git a/benchmarks/LiftBench.hs b/benchmarks/LiftBench.hs
--- a/benchmarks/LiftBench.hs
+++ b/benchmarks/LiftBench.hs
@@ -6,15 +6,13 @@
 import Control.Monad.Identity
 import qualified Control.Monad.Trans.Reader as R
 import qualified Control.Monad.Trans.State.Strict as S
-import qualified Control.Monad.Trans.Writer.Strict as W
-import qualified Control.Monad.Trans.RWS.Strict as RWS
 import Criterion.Main
 import Data.Monoid
 import Pipes
 import Pipes.Lift
 
 defaultMax :: Int
-defaultMax = 1000000
+defaultMax = 10000
 
 instance NFData a => NFData (Sum a)
 
@@ -33,22 +31,9 @@
 s_bench :: Int -> Effect (S.StateT Int Identity) Int
 s_bench = iter (\n -> S.get >>= (\a -> S.put $! a + n) >> return (n + 1))
 
-w_bench :: Int -> Effect (W.WriterT (Sum Int) Identity) Int
-w_bench = iter (\n -> (W.tell $! Sum n) >> return (n + 1))
-
 r_bench :: Int -> Effect (R.ReaderT Int Identity) Int
 r_bench = iter (\n -> R.ask >>= (\a -> return $ n + a))
 
-rwsp_bench :: Int -> Effect (RWS.RWST Int (Sum Int) Int Identity) Int
-rwsp_bench = iter act
-    where
-        act n = do
-            x <- RWS.ask
-            RWS.tell (Sum n)
-            s <- RWS.get
-            RWS.put $! (s + n)
-            return $ n + x
-
 -- Run before Proxy
 runB :: (a -> Effect Identity r) -> a -> r
 runB f a = runIdentity $ runEffect $ f a
@@ -79,22 +64,5 @@
         , bench "evalStateP_A" . whnf (runA (defT S.evalStateT) . s_bench)
         , bench "execStateP_B" . whnf (runB (execStateP 0) . s_bench)
         , bench "execStateP_A" . whnf (runA (defT S.execStateT) . s_bench)
-        ]
-    , bgroup "WriterT" $ applyBench
-        [
-        -- Running WriterP after runEffect will space leak.
-          bench "runWriterP_B"  . nf (runB runWriterP . w_bench)
-        , bench "execWriterP_B" . nf (runB execWriterP . w_bench)
-        ]
-    , bgroup "RWSP" $
-        let defT f = (\d -> f d 1 0)
-        in applyBench
-        [
-          bench "runRWSP_B"  . nf (runB (runRWSP 1 0) . rwsp_bench)
-        , bench "runRWSP_A"  . nf (runA (defT RWS.runRWST) . rwsp_bench)
-        , bench "evalRWSP_B" . nf (runB (evalRWSP 1 0) . rwsp_bench)
-        , bench "evalRWSP_A" . nf (runA (defT RWS.evalRWST) . rwsp_bench)
-        , bench "execRWSP_B" . nf (runB (execRWSP 1 0) . rwsp_bench)
-        , bench "execRWSP_A" . nf (runA (defT RWS.execRWST) . rwsp_bench)
         ]
     ]
diff --git a/benchmarks/PreludeBench.hs b/benchmarks/PreludeBench.hs
--- a/benchmarks/PreludeBench.hs
+++ b/benchmarks/PreludeBench.hs
@@ -9,7 +9,7 @@
 import Prelude hiding (enumFromTo)
 
 defaultMax :: Int
-defaultMax = 1000000
+defaultMax = 10000
 
 main :: IO ()
 main = commonMain defaultMax preludeBenchmarks
diff --git a/pipes.cabal b/pipes.cabal
--- a/pipes.cabal
+++ b/pipes.cabal
@@ -1,5 +1,5 @@
 Name: pipes
-Version: 4.0.0
+Version: 4.0.1
 Cabal-Version: >= 1.10
 Build-Type: Simple
 License: BSD3
@@ -39,14 +39,22 @@
     Location: https://github.com/Gabriel439/Haskell-Pipes-Library
 
 Library
-    Default-Language: Haskell2010
+    if !flag(haskell98)
+        Default-Language: Haskell2010
+    else
+        Default-Language: Haskell98
+
     HS-Source-Dirs: src
     Build-Depends:
         base         >= 4       && < 5  ,
-        mmorph       >= 1.0.0   && < 1.1,
-        mtl          >= 2.0.1.0 && < 2.2,
         transformers >= 0.2.0.0 && < 0.4,
         void                       < 0.7
+
+    if !flag(haskell98)
+        Build-Depends:
+            mmorph       >= 1.0.0   && < 1.1,
+            mtl          >= 2.0.1.0 && < 2.2
+
     Exposed-Modules:
         Pipes,
         Pipes.Core,
@@ -56,6 +64,10 @@
         Pipes.Tutorial
     GHC-Options: -O2 -Wall
 
+    if flag(haskell98)
+      CPP-Options: -Dhaskell98
+
+
 Benchmark prelude-benchmarks
     Default-Language: Haskell2010
     Type:             exitcode-stdio-1.0
@@ -69,6 +81,22 @@
         mtl       >= 2.0.1.0 && < 2.2,
         pipes     >= 4.0.0   && < 4.1
 
+test-suite tests
+    Default-Language: Haskell2010
+    Type:             exitcode-stdio-1.0
+    HS-Source-Dirs:   tests
+    Main-Is:          Main.hs
+    GHC-Options:      -Wall -threaded -rtsopts -with-rtsopts=-N -fno-warn-missing-signatures
+
+    Build-Depends:
+        base                       >= 4       && < 5   ,
+        pipes                      >= 4.0.0   && < 4.1 ,
+        QuickCheck                 >= 2.4     && < 3   ,
+        mtl                        >= 2.0.1   && < 2.2 ,
+        test-framework             >= 0.4     && < 1   ,
+        test-framework-quickcheck2 >= 0.2.0   && < 0.4 ,
+        transformers               >= 0.2.0.0 && < 0.4
+
 Benchmark lift-benchmarks
     Default-Language: Haskell2010
     Type:             exitcode-stdio-1.0
@@ -83,3 +111,7 @@
         mtl          >= 2.0.1.0 && < 2.2,
         pipes        >= 4.0.0   && < 4.1,
         transformers >= 0.2.0.0 && < 0.4
+
+Flag haskell98
+  Description: Haskell98 compliant subset of pipes.
+  Default:     False
diff --git a/src/Pipes.hs b/src/Pipes.hs
--- a/src/Pipes.hs
+++ b/src/Pipes.hs
@@ -4,79 +4,96 @@
     library.
 -}
 
-{-# LANGUAGE RankNTypes, CPP #-}
+{-# LANGUAGE
+    RankNTypes
+  , CPP
+  , FlexibleInstances
+  , MultiParamTypeClasses
+  , UndecidableInstances
+  #-}
 
+-- The rewrite RULES require the 'TrustWorthy' annotation
 #if __GLASGOW_HASKELL__ >= 702
 {-# LANGUAGE Trustworthy #-}
 #endif
-{- The rewrite RULES require the 'TrustWorthy' annotation. -}
 
 module Pipes (
     -- * The Proxy Monad Transformer
-    Proxy,
-    Effect,
-    Effect',
-    runEffect,
+      Proxy
+    , Effect
+    , Effect'
+    , runEffect
 
     -- ** Producers
     -- $producers
-    Producer,
-    Producer',
-    yield,
-    for,
-    (~>),
-    (<~),
+    , Producer
+    , Producer'
+    , yield
+    , for
+    , (~>)
+    , (<~)
 
     -- ** Consumers
     -- $consumers
-    Consumer,
-    Consumer',
-    await,
-    (>~),
-    (~<),
+    , Consumer
+    , Consumer'
+    , await
+    , (>~)
+    , (~<)
 
     -- ** Pipes
     -- $pipes
-    Pipe,
-    cat,
-    (>->),
-    (<-<),
+    , Pipe
+    , cat
+    , (>->)
+    , (<-<)
 
     -- * ListT
-    ListT(..),
-    Enumerable(..),
+    , ListT(..)
+    , Enumerable(..)
 
     -- * Utilities
-    next,
-    each,
-    every,
-    discard,
+    , next
+    , each
+    , every
+    , discard
 
     -- * Re-exports
     -- $reexports
-    module Control.Monad.IO.Class,
-    module Control.Monad.Trans.Class,
-    module Control.Monad.Morph,
-    module Data.Foldable,
-    module Data.Void
+    , module Control.Monad.IO.Class
+    , module Control.Monad.Trans.Class
+#ifndef haskell98
+    , module Control.Monad.Morph
+#endif
+    , module Data.Foldable
+    , module Data.Void
     ) where
 
 import Control.Applicative (Applicative(pure, (<*>)), Alternative(empty, (<|>)))
 import Control.Monad (MonadPlus(mzero, mplus))
-import Control.Monad.IO.Class (MonadIO(liftIO))
-import Control.Monad.Trans.Class (MonadTrans(lift))
+import Control.Monad.IO.Class (MonadIO(liftIO)) -- transformers
+import Control.Monad.Trans.Class (MonadTrans(lift)) --transformers
 import Control.Monad.Trans.Error (ErrorT(runErrorT))
-import Control.Monad.Trans.Identity (IdentityT(runIdentityT))
-import Control.Monad.Trans.Maybe (MaybeT(runMaybeT))
+import Control.Monad.Trans.Identity (IdentityT(runIdentityT)) --transformers
+import Control.Monad.Trans.Maybe (MaybeT(runMaybeT)) --transformers
 import Data.Foldable (Foldable)
 import qualified Data.Foldable as F
+import Data.Monoid (Monoid(..))
 import Data.Void (Void)
 import qualified Data.Void as V
 import Pipes.Internal (Proxy(..))
 import Pipes.Core
+#ifndef haskell98
+import Control.Monad.Error (MonadError(..))
+import Control.Monad.Reader (MonadReader(..))
+import Control.Monad.State (MonadState(..))
+import Control.Monad.Writer (MonadWriter(..))
+#endif
 
 -- Re-exports
+#ifndef haskell98
 import Control.Monad.Morph (MFunctor(hoist))
+#endif
 
 infixl 4 <~
 infixr 4 ~>
@@ -132,8 +149,6 @@
 @
 'for' :: 'Monad' m => 'Producer' b m r -> (b -> 'Effect'       m ()) -> 'Effect'       m r
 'for' :: 'Monad' m => 'Producer' b m r -> (b -> 'Producer'   c m ()) -> 'Producer'   c m r
-'for' :: 'Monad' m => 'Pipe'   x b m r -> (b -> 'Effect'       m ()) -> 'Consumer' x   m r
-'for' :: 'Monad' m => 'Pipe'   x b m r -> (b -> 'Producer'   c m ()) -> 'Pipe'     x c m r
 'for' :: 'Monad' m => 'Pipe'   x b m r -> (b -> 'Consumer' x   m ()) -> 'Consumer' x   m r
 'for' :: 'Monad' m => 'Pipe'   x b m r -> (b -> 'Pipe'     x c m ()) -> 'Pipe'     x c m r
 @
@@ -148,13 +163,26 @@
 {-# INLINABLE for #-}
 
 {-# RULES
-    "for cat f" forall f .
+    "for (for p f) g" forall p f g . for (for p f) g = for p (\a -> for (f a) g)
+
+  ; "for p yield" forall p . for p yield = p
+
+  ; "for (yield x) f" forall x f . for (yield x) f = f x
+
+  ; "for cat f" forall f .
         for cat f =
             let go = do
                     x <- await
                     f x
                     go
             in  go
+
+  ; "f >~ (g >~ p)" forall f g p . f >~ (g >~ p) = (f >~ g) >~ p
+
+  ; "await >~ p" forall p . await >~ p = p
+
+  ; "p >~ await" forall p . p >~ await = p
+
   ; "m >~ cat" forall m .
         m >~ cat =
             let go = do
@@ -169,8 +197,6 @@
 @
 ('~>') :: 'Monad' m => (a -> 'Producer' b m r) -> (b -> 'Effect'       m ()) -> (a -> 'Effect'       m r)
 ('~>') :: 'Monad' m => (a -> 'Producer' b m r) -> (b -> 'Producer'   c m ()) -> (a -> 'Producer'   c m r)
-('~>') :: 'Monad' m => (a -> 'Pipe'   x b m r) -> (b -> 'Effect'       m ()) -> (a -> 'Consumer' x   m r)
-('~>') :: 'Monad' m => (a -> 'Pipe'   x b m r) -> (b -> 'Producer'   c m ()) -> (a -> 'Pipe'     x c m r)
 ('~>') :: 'Monad' m => (a -> 'Pipe'   x b m r) -> (b -> 'Consumer' x   m ()) -> (a -> 'Consumer' x   m r)
 ('~>') :: 'Monad' m => (a -> 'Pipe'   x b m r) -> (b -> 'Pipe'     x c m ()) -> (a -> 'Pipe'     x c m r)
 @
@@ -229,8 +255,6 @@
 @
 ('>~') :: 'Monad' m => 'Effect'       m b -> 'Consumer' b   m c -> 'Effect'       m c
 ('>~') :: 'Monad' m => 'Consumer' a   m b -> 'Consumer' b   m c -> 'Consumer' a   m c
-('>~') :: 'Monad' m => 'Effect'       m b -> 'Pipe'     b y m c -> 'Producer'   y m c
-('>~') :: 'Monad' m => 'Consumer' a   m b -> 'Pipe'     b y m c -> 'Pipe'     a y m c
 ('>~') :: 'Monad' m => 'Producer'   y m b -> 'Pipe'     b y m c -> 'Producer'   y m c
 ('>~') :: 'Monad' m => 'Pipe'     a y m b -> 'Pipe'     b y m c -> 'Pipe'     a y m c
 @
@@ -320,6 +344,7 @@
 instance (Monad m) => Monad (ListT m) where
     return a = Select (yield a)
     m >>= f  = Select (for (enumerate m) (\a -> enumerate (f a)))
+    fail _   = mzero
 
 instance MonadTrans ListT where
     lift m = Select (do
@@ -339,9 +364,68 @@
     mzero = empty
     mplus = (<|>)
 
+#ifndef haskell98
 instance MFunctor ListT where
     hoist morph = Select . hoist morph . enumerate
+#endif
 
+instance (Monad m) => Monoid (ListT m a) where
+    mempty = empty
+    mappend = (<|>)
+
+#ifndef haskell98
+instance (MonadState s m) => MonadState s (ListT m) where
+    get     = lift  get
+
+    put   s = lift (put   s)
+
+#if MIN_VERSION_mtl(2,1,0)
+    state f = lift (state f)
+#endif
+
+instance (MonadWriter w m) => MonadWriter w (ListT m) where
+#if MIN_VERSION_mtl(2,1,0)
+    writer = lift . writer
+#endif
+
+    tell w = lift (tell w)
+
+    listen l = Select (go (enumerate l) mempty)
+      where
+        go p w = case p of
+            Request a' fa  -> Request a' (\a  -> go (fa  a ) w)
+            Respond b  fb' -> Respond (b, w)  (\b' -> go (fb' b') w)
+            M          m   -> M (do
+                (p', w') <- listen m
+                return (go p' $! mappend w w') )
+            Pure    r      -> Pure r
+
+    pass l = Select (go (enumerate l) mempty)
+      where
+        go p w = case p of
+            Request  a'     fa  -> Request a' (\a  -> go (fa  a ) w)
+            Respond (b, f)  fb' -> M (pass (return
+                (Respond b (\b' -> go (fb' b') (f w)), \_ -> f w) ))
+            M               m   -> M (do
+                (p', w') <- listen m
+                return (go p' $! mappend w w') )
+            Pure    r           -> Pure r
+
+instance (MonadReader i m) => MonadReader i (ListT m) where
+    ask = lift ask
+
+    local f l = Select (local f (enumerate l))
+
+#if MIN_VERSION_mtl(2,1,0)
+    reader f = lift (reader f)
+#endif
+
+instance (MonadError e m) => MonadError e (ListT m) where
+    throwError e = lift (throwError e)
+
+    catchError l k = Select (catchError (enumerate l) (\e -> enumerate (k e)))
+#endif
+
 {-| 'Enumerable' generalizes 'Data.Foldable.Foldable', converting effectful
     containers to 'ListT's.
 -}
@@ -416,8 +500,10 @@
 
     "Control.Monad.Trans.Class" re-exports 'MonadTrans'.
 
+#ifndef haskell98
     "Control.Monad.Morph" re-exports 'MFunctor'.
 
+#endif
     "Data.Foldable" re-exports 'Foldable' (the class name only)
 
     "Data.Void" re-exports 'Void'.
diff --git a/src/Pipes/Core.hs b/src/Pipes/Core.hs
--- a/src/Pipes/Core.hs
+++ b/src/Pipes/Core.hs
@@ -15,76 +15,72 @@
 
 {-# LANGUAGE CPP, RankNTypes #-}
 
+-- The rewrite RULES require the 'TrustWorthy' annotation
 #if __GLASGOW_HASKELL__ >= 702
 {-# LANGUAGE Trustworthy #-}
 #endif
-{- The rewrite RULES require the 'TrustWorthy' annotation.  Their proofs are
-   pretty trivial since they are just inlining the definition of their
-   respective operators.  GHC doesn't do this inlining automatically for these
-   functions because they are recursive.
--}
 
 module Pipes.Core (
     -- * Proxy Monad Transformer
     -- $proxy
-    Proxy,
-    runEffect,
+      Proxy
+    , runEffect
 
     -- * Categories
     -- $categories
 
     -- ** Respond
     -- $respond
-    respond,
-    (/>/),
-    (//>),
+    , respond
+    , (/>/)
+    , (//>)
 
     -- ** Request
     -- $request
-    request,
-    (\>\),
-    (>\\),
+    , request
+    , (\>\)
+    , (>\\)
 
     -- ** Push
     -- $push
-    push,
-    (>~>),
-    (>>~),
+    , push
+    , (>~>)
+    , (>>~)
 
     -- ** Pull
     -- $pull
-    pull,
-    (>+>),
-    (+>>),
+    , pull
+    , (>+>)
+    , (+>>)
 
     -- ** Reflect
     -- $reflect
-    reflect,
+    , reflect
 
     -- * Concrete Type Synonyms
-    Effect,
-    Producer,
-    Pipe,
-    Consumer,
-    Client,
-    Server,
+    , Effect
+    , Producer
+    , Pipe
+    , Consumer
+    , Client
+    , Server
 
     -- * Polymorphic Type Synonyms
-    Effect',
-    Producer',
-    Consumer',
-    Client',
-    Server',
+    , Effect'
+    , Producer'
+    , Consumer'
+    , Client'
+    , Server'
 
     -- * Flipped operators
-    (\<\),
-    (/</),
-    (<~<),
-    (~<<),
-    (<+<),
-    (<\\),
-    (//<),
-    (<<+)
+    , (\<\)
+    , (/</)
+    , (<~<)
+    , (~<<)
+    , (<+<)
+    , (<\\)
+    , (//<)
+    , (<<+)
     ) where
 
 import Data.Void (Void, absurd)
@@ -131,8 +127,7 @@
         Pure    r   -> return r
 {-# INLINABLE runEffect #-}
 
-{-
-   * Keep proxy composition lower in precedence than function composition, which
+{- * Keep proxy composition lower in precedence than function composition, which
      is 9 at the time of of this comment, so that users can write things like:
 
 
@@ -832,3 +827,18 @@
     -- ^
 k <<+ p = p +>> k
 {-# INLINABLE (<<+) #-}
+
+{-# RULES
+    "(p //> f) //> g" forall p f g . (p //> f) //> g = p //> (\a -> f a //> g)
+
+  ; "p //> respond" forall p . p //> respond = p
+
+  ; "respond x //> f" forall x f . respond x //>  f = f x
+
+  ; "f >\\ (g >\\ p)" forall f g p . f >\\ (g >\\ p) = (\a -> f >\\ g a) >\\ p
+
+  ; "request >\\ p" forall p . request >\\ p = p
+
+  ; "f >\\ request x" forall f x . f >\\ request x = f x
+
+  #-}
diff --git a/src/Pipes/Internal.hs b/src/Pipes/Internal.hs
--- a/src/Pipes/Internal.hs
+++ b/src/Pipes/Internal.hs
@@ -25,23 +25,31 @@
   , UndecidableInstances
   , CPP
   #-}
+
+-- The rewrite RULES require the 'TrustWorthy' annotation
+#if __GLASGOW_HASKELL__ >= 702
+{-# LANGUAGE Trustworthy #-}
+#endif
+
 module Pipes.Internal (
     -- * Internal
-    Proxy(..),
-    unsafeHoist,
-    observe,
+      Proxy(..)
+    , unsafeHoist
+    , observe,
     ) where
 
 import Control.Applicative (Applicative(pure, (<*>)), Alternative(empty, (<|>)))
-import Control.Monad (liftM, MonadPlus(..))
+import Control.Monad (MonadPlus(..))
 import Control.Monad.IO.Class (MonadIO(liftIO))
-import Control.Monad.Morph (MFunctor(hoist))
 import Control.Monad.Trans.Class (MonadTrans(lift))
+#ifndef haskell98
+import Control.Monad.Morph (MFunctor(hoist))
 import Control.Monad.Error (MonadError(..))
 import Control.Monad.Reader (MonadReader(..))
 import Control.Monad.State (MonadState(..))
 import Control.Monad.Writer (MonadWriter(..))
 import Data.Monoid (mempty,mappend)
+#endif
 
 {-| A 'Proxy' is a monad transformer that receives and sends information on both
     an upstream and downstream interface.
@@ -127,7 +135,9 @@
         Respond b  fb' -> Respond b  (\b' -> go (fb' b'))
         M          m   -> M (nat (m >>= \p' -> return (go p')))
         Pure       r   -> Pure r
+{-# INLINABLE unsafeHoist #-}
 
+#ifndef haskell98
 instance MFunctor (Proxy a' a b' b) where
     hoist nat p0 = go (observe p0) where
         go p = case p of
@@ -135,10 +145,12 @@
             Respond b  fb' -> Respond b  (\b' -> go (fb' b'))
             M          m   -> M (nat (m >>= \p' -> return (go p')))
             Pure       r   -> Pure r
+#endif
 
 instance (MonadIO m) => MonadIO (Proxy a' a b' b m) where
     liftIO m = M (liftIO (m >>= \r -> return (Pure r)))
 
+#ifndef haskell98
 instance (MonadReader r m) => MonadReader r (Proxy a' a b' b m) where
     ask = lift ask
     local f = go
@@ -147,10 +159,9 @@
               Request a' fa  -> Request a' (\a  -> go (fa  a ))
               Respond b  fb' -> Respond b  (\b' -> go (fb' b'))
               Pure    r      -> Pure r
-              M       m      -> M (go `liftM` local f m)
+              M       m      -> M (local f m >>= \r -> return (go r))
 #if MIN_VERSION_mtl(2,1,0)
     reader = lift . reader
-#else
 #endif
 
 instance (MonadState s m) => MonadState s (Proxy a' a b' b m) where
@@ -158,31 +169,32 @@
     put = lift . put
 #if MIN_VERSION_mtl(2,1,0)
     state = lift . state
-#else
 #endif
 
 instance (MonadWriter w m) => MonadWriter w (Proxy a' a b' b m) where
 #if MIN_VERSION_mtl(2,1,0)
     writer = lift . writer
-#else
 #endif
     tell = lift . tell
-    listen proxy = go proxy mempty
-        where
-          go p w = case p of
-              Request a' fa  -> Request a' (\a  -> go (fa  a ) w)
-              Respond b  fb' -> Respond b  (\b' -> go (fb' b') w)
-              Pure    r      -> Pure (r, w)
-              M       m      -> M (
-                (\(p', w') -> go p' $! mappend w w') `liftM` listen m)
+    listen p0 = go p0 mempty
+      where
+        go p w = case p of
+            Request a' fa  -> Request a' (\a  -> go (fa  a ) w)
+            Respond b  fb' -> Respond b  (\b' -> go (fb' b') w)
+            M       m      -> M (do
+                (p', w') <- listen m
+                return (go p' $! mappend w w') )
+            Pure    r      -> Pure (r, w)
 
-    pass = go
-        where
-          go p = case p of
-              Request a' fa  -> Request a' (\a  -> go (fa  a ))
-              Respond b  fb' -> Respond b  (\b' -> go (fb' b'))
-              M       m      -> M (go `liftM` m)
-              Pure    (r, f) -> M (pass (return (Pure r, f)))
+    pass p0 = go p0 mempty
+      where
+        go p w = case p of
+            Request a' fa  -> Request a' (\a  -> go (fa  a ) w)
+            Respond b  fb' -> Respond b  (\b' -> go (fb' b') w)
+            M       m      -> M (do
+                (p', w') <- listen m
+                return (go p' $! mappend w w') )
+            Pure    (r, f) -> M (pass (return (Pure r, \_ -> f w)))
 
 instance (MonadError e m) => MonadError e (Proxy a' a b' b m) where
     throwError = lift . throwError
@@ -195,6 +207,7 @@
             M          m   -> M ((do
                 p' <- m
                 return (go p') ) `catchError` (\e -> return (f e)) )
+#endif
 
 instance (MonadPlus m) => Alternative (Proxy a' a b' b m) where
     empty = mzero
diff --git a/src/Pipes/Lift.hs b/src/Pipes/Lift.hs
--- a/src/Pipes/Lift.hs
+++ b/src/Pipes/Lift.hs
@@ -7,47 +7,67 @@
 
 module Pipes.Lift (
     -- * ErrorT
-    errorP,
-    runErrorP,
-    catchError,
-    liftCatchError,
+      errorP
+#ifndef haskell98
+    , runErrorP
+    , catchError
+#endif
+    , liftCatchError
 
     -- * MaybeT
-    maybeP,
-    runMaybeP,
+    , maybeP
+#ifndef haskell98
+    , runMaybeP
+#endif
 
     -- * ReaderT
-    readerP,
-    runReaderP,
+    , readerP
+#ifndef haskell98
+    , runReaderP
+#endif
 
     -- * StateT
-    stateP,
-    runStateP,
-    evalStateP,
-    execStateP,
+    , stateP
+#ifndef haskell98
+    , runStateP
+    , evalStateP
+    , execStateP
+#endif
 
     -- * WriterT
     -- $writert
-    writerP,
-    runWriterP,
-    execWriterP,
+    , writerP
+#ifndef haskell98
+    , runWriterP
+    , execWriterP
+#endif
 
     -- * RWST
-    rwsP,
-    runRWSP,
-    evalRWSP,
-    execRWSP
+    , rwsP
+#ifndef haskell98
+    , runRWSP
+    , evalRWSP
+    , execRWSP
+
+    -- * Utilities
+    , distribute
+#endif
+
     ) where
 
-import Control.Monad.Trans.Class (lift)
+import Control.Monad.Trans.Class (lift, MonadTrans(..))
 import qualified Control.Monad.Trans.Error as E
 import qualified Control.Monad.Trans.Maybe as M
 import qualified Control.Monad.Trans.Reader as R
 import qualified Control.Monad.Trans.State.Strict as S
 import qualified Control.Monad.Trans.Writer.Strict as W
 import qualified Control.Monad.Trans.RWS.Strict as RWS
-import Data.Monoid (Monoid(mempty, mappend))
-import Pipes.Internal
+import Data.Monoid (Monoid)
+import Pipes.Internal (Proxy(..), unsafeHoist)
+#ifndef haskell98
+import Control.Monad.Morph (hoist, MFunctor(..))
+import Pipes.Core (runEffect, request, respond, (//>), (>\\))
+#endif
 
 -- | Wrap the base monad in 'E.ErrorT'
 errorP
@@ -59,43 +79,27 @@
     lift $ E.ErrorT (return x)
 {-# INLINABLE errorP #-}
 
+#ifndef haskell98
 -- | Run 'E.ErrorT' in the base monad
 runErrorP
-    :: (Monad m)
-    => Proxy a' a b' b (E.ErrorT e m) r -> Proxy a' a b' b m (Either e r)
-runErrorP = go
-  where
-    go p = case p of
-        Request a' fa  -> Request a' (\a  -> go (fa  a ))
-        Respond b  fb' -> Respond b  (\b' -> go (fb' b'))
-        Pure    r      -> Pure (Right r)
-        M          m   -> M (do
-            x <- E.runErrorT m
-            return (case x of
-                Left  e  -> Pure (Left e)
-                Right p' -> go p' ) )
+    :: (Monad m, E.Error e)
+    => Proxy a' a b' b (E.ErrorT e m) r
+    -> Proxy a' a b' b m (Either e r)
+runErrorP    = E.runErrorT . distribute 
 {-# INLINABLE runErrorP #-}
 
 -- | Catch an error in the base monad
 catchError
-    :: (Monad m) 
+    :: (Monad m, E.Error e) 
     => Proxy a' a b' b (E.ErrorT e m) r
     -- ^
-    -> (e -> Proxy a' a b' b (E.ErrorT f m) r)
+    -> (e -> Proxy a' a b' b (E.ErrorT e m) r)
     -- ^
-    -> Proxy a' a b' b (E.ErrorT f m) r
-catchError p0 f = go p0
-  where
-    go p = case p of
-        Request a' fa  -> Request a' (\a  -> go (fa  a ))
-        Respond b  fb' -> Respond b  (\b' -> go (fb' b'))
-        Pure    r      -> Pure r
-        M          m   -> M (E.ErrorT (do
-            x <- E.runErrorT m
-            return (Right (case x of
-                Left  e  -> f  e
-                Right p' -> go p' )) ))
+    -> Proxy a' a b' b (E.ErrorT e m) r
+catchError e h = errorP . E.runErrorT $ 
+    E.catchError (distribute e) (distribute . h)
 {-# INLINABLE catchError #-}
+#endif
 
 -- | Catch an error using a catch function for the base monad
 liftCatchError
@@ -128,22 +132,15 @@
     lift $ M.MaybeT (return x)
 {-# INLINABLE maybeP #-}
 
+#ifndef haskell98
 -- | Run 'M.MaybeT' in the base monad
 runMaybeP
     :: (Monad m)
-    => Proxy a' a b' b (M.MaybeT m) r -> Proxy a' a b' b m (Maybe r)
-runMaybeP = go
-  where
-    go p = case p of
-        Request a' fa  -> Request a' (\a  -> go (fa  a ))
-        Respond b  fb' -> Respond b  (\b' -> go (fb' b'))
-        Pure    r      -> Pure (Just r)
-        M          m   -> M (do
-            x <- M.runMaybeT m
-            return (case x of
-                Nothing -> Pure Nothing
-                Just p' -> go p' ) )
+    => Proxy a' a b' b (M.MaybeT m) r
+    -> Proxy a' a b' b m (Maybe r)
+runMaybeP p = M.runMaybeT $ distribute p
 {-# INLINABLE runMaybeP #-}
+#endif
 
 -- | Wrap the base monad in 'R.ReaderT'
 readerP
@@ -154,20 +151,16 @@
     unsafeHoist lift (k i)
 {-# INLINABLE readerP #-}
 
+#ifndef haskell98
 -- | Run 'R.ReaderT' in the base monad
 runReaderP
     :: (Monad m)
-    => i -> Proxy a' a b' b (R.ReaderT i m) r -> Proxy a' a b' b m r
-runReaderP i = go
-  where
-    go p = case p of
-        Request a' fa  -> Request a' (\a  -> go (fa  a ))
-        Respond b  fb' -> Respond b  (\b' -> go (fb' b'))
-        Pure    r      -> Pure r
-        M          m   -> M (do
-            p' <- R.runReaderT m i
-            return (go p') )
+    => i
+    -> Proxy a' a b' b (R.ReaderT i m) r
+    -> Proxy a' a b' b m r
+runReaderP r p = (`R.runReaderT` r) $ distribute p
 {-# INLINABLE runReaderP #-}
+#endif
 
 -- | Wrap the base monad in 'S.StateT'
 stateP
@@ -180,32 +173,34 @@
     return r
 {-# INLINABLE stateP #-}
 
+#ifndef haskell98
 -- | Run 'S.StateT' in the base monad
 runStateP
     :: (Monad m)
-    => s -> Proxy a' a b' b (S.StateT s m) r -> Proxy a' a b' b m (r, s)
-runStateP = go
-  where
-    go s p = case p of
-        Request a' fa  -> Request a' (\a  -> go s (fa  a ))
-        Respond b  fb' -> Respond b  (\b' -> go s (fb' b'))
-        Pure    r      -> Pure (r, s)
-        M          m   -> M (do
-            (p', s') <- S.runStateT m s
-            return (go s' p') )
+    => s
+    -> Proxy a' a b' b (S.StateT s m) r
+    -> Proxy a' a b' b m (r, s)
+runStateP s p = (`S.runStateT` s) $ distribute p
 {-# INLINABLE runStateP #-}
 
 -- | Evaluate 'S.StateT' in the base monad
 evalStateP
-    :: (Monad m) => s -> Proxy a' a b' b (S.StateT s m) r -> Proxy a' a b' b m r
-evalStateP s = fmap fst . runStateP s
+    :: (Monad m)
+    => s
+    -> Proxy a' a b' b (S.StateT s m) r
+    -> Proxy a' a b' b m r
+evalStateP s p = fmap fst $ runStateP s p
 {-# INLINABLE evalStateP #-}
 
 -- | Execute 'S.StateT' in the base monad
 execStateP
-    :: (Monad m) => s -> Proxy a' a b' b (S.StateT s m) r -> Proxy a' a b' b m s
-execStateP s = fmap snd . runStateP s
+    :: (Monad m)
+    => s
+    -> Proxy a' a b' b (S.StateT s m) r
+    -> Proxy a' a b' b m s
+execStateP s p = fmap snd $ runStateP s p
 {-# INLINABLE execStateP #-}
+#endif
 
 {- $writert
     Note that 'runWriterP' and 'execWriterP' will keep the accumulator in
@@ -228,28 +223,23 @@
     return r
 {-# INLINABLE writerP #-}
 
+#ifndef haskell98
 -- | Run 'W.WriterT' in the base monad
 runWriterP
-    :: (Monad m, Monoid w)
-    => Proxy a' a b' b (W.WriterT w m) r -> Proxy a' a b' b m (r, w)
-runWriterP = go mempty
-  where
-    go w p = case p of
-        Request a' fa  -> Request a' (\a  -> go w (fa  a ))
-        Respond b  fb' -> Respond b  (\b' -> go w (fb' b'))
-        Pure  r      -> Pure (r, w)
-        M        m   -> M (do
-            (p', w') <- W.runWriterT m
-            let wt = mappend w w'
-            wt `seq` return (go wt p') )
+    :: (Monad m, Data.Monoid.Monoid w)
+    => Proxy a' a b' b (W.WriterT w m) r
+    -> Proxy a' a b' b m (r, w)
+runWriterP p = W.runWriterT $ distribute p
 {-# INLINABLE runWriterP #-}
 
 -- | Execute 'W.WriterT' in the base monad
 execWriterP
-    :: (Monad m, Monoid w)
-    => Proxy a' a b' b (W.WriterT w m) r -> Proxy a' a b' b m w
-execWriterP = fmap snd . runWriterP
+    :: (Monad m, Data.Monoid.Monoid w)
+    => Proxy a' a b' b (W.WriterT w m) r
+    -> Proxy a' a b' b m w
+execWriterP p = fmap snd $ runWriterP p
 {-# INLINABLE execWriterP #-}
+#endif
 
 -- | Wrap the base monad in 'RWS.RWST'
 rwsP
@@ -266,40 +256,56 @@
     return r
 {-# INLINABLE rwsP #-}
 
+#ifndef haskell98
 -- | Run 'RWS.RWST' in the base monad
-runRWSP :: (Monad m, Monoid w)
-        => i
-        -> s
-        -> Proxy a' a b' b (RWS.RWST i w s m) r
-        -> Proxy a' a b' b m (r, s, w)
-runRWSP i = go mempty
-  where
-    go w s p = case p of
-        Request a' fa  -> Request a' (\a  -> go w s (fa  a ))
-        Respond b  fb' -> Respond b  (\b' -> go w s (fb' b'))
-        Pure    r      -> Pure (r, s, w)
-        M          m   -> M (do
-            (p', s', w') <- RWS.runRWST m i s
-            let wt = mappend w w'
-            wt `seq` return (go w' s' p') )
+runRWSP
+    :: (Monad m, Monoid w)
+    => r
+    -> s
+    -> Proxy a' a b' b (RWS.RWST r w s m) d
+    -> Proxy a' a b' b m (d, s, w)
+runRWSP  i s p = (\b -> RWS.runRWST b i s) $ distribute p
 {-# INLINABLE runRWSP #-}
 
 -- | Evaluate 'RWS.RWST' in the base monad
-evalRWSP :: (Monad m, Monoid w)
-         => i
-         -> s
-         -> Proxy a' a b' b (RWS.RWST i w s m) r
-         -> Proxy a' a b' b m (r, w)
-evalRWSP i s = fmap go . runRWSP i s
-    where go (r, _, w) = (r, w)
+evalRWSP
+    :: (Monad m, Monoid w)
+    => r
+    -> s
+    -> Proxy a' a b' b (RWS.RWST r w s m) d
+    -> Proxy a' a b' b m (d, w)
+evalRWSP i s p = fmap f $ runRWSP i s p
+  where
+    f x = let (r, _, w) = x in (r, w)
 {-# INLINABLE evalRWSP #-}
 
 -- | Execute 'RWS.RWST' in the base monad
-execRWSP :: (Monad m, Monoid w)
-         => i
-         -> s
-         -> Proxy a' a b' b (RWS.RWST i w s m) r
-         -> Proxy a' a b' b m (s, w)
-execRWSP i s = fmap go . runRWSP i s
-    where go (_, s', w) = (s', w)
+execRWSP
+    :: (Monad m, Monoid w)
+    => r
+    -> s
+    -> Proxy a' a b' b (RWS.RWST r w s m) d
+    -> Proxy a' a b' b m (s, w)
+execRWSP i s p = fmap f $ runRWSP i s p
+  where
+    f x = let (_, s', w) = x in (s', w)
 {-# INLINABLE execRWSP #-}
+
+-- | Distribute 'Proxy' over a monad transformer
+distribute
+    ::  ( Monad m
+        , MonadTrans t
+        , MFunctor t
+        , Monad (t m)
+        , Monad (t (Proxy a' a b' b m))
+        )
+    => Proxy a' a b' b (t m) r
+    -- ^ 
+    -> t (Proxy a' a b' b m) r
+    -- ^ 
+distribute p =  runEffect $ request' >\\ unsafeHoist (hoist lift) p //> respond'
+  where
+    request' = lift . lift . request
+    respond' = lift . lift . respond
+{-# INLINABLE distribute #-}
+#endif
diff --git a/src/Pipes/Prelude.hs b/src/Pipes/Prelude.hs
--- a/src/Pipes/Prelude.hs
+++ b/src/Pipes/Prelude.hs
@@ -15,117 +15,127 @@
     'Text' utilities for @pipes@ will preserve newlines.
 -}
 
-{-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE RankNTypes, CPP #-}
 {-# OPTIONS_GHC -fno-warn-unused-do-bind #-}
 
+-- The rewrite RULES require the 'TrustWorthy' annotation
+#if __GLASGOW_HASKELL__ >= 702
+{-# LANGUAGE Trustworthy #-}
+#endif
+
 module Pipes.Prelude (
     -- * Producers
     -- $producers
-    stdinLn,
-    readLn,
-    fromHandle,
-    replicateM,
+      stdinLn
+    , readLn
+    , fromHandle
+    , replicateM
 
     -- * Consumers
     -- $consumers
-    stdoutLn,
-    print,
-    toHandle,
+    , stdoutLn
+    , print
+    , toHandle
 
     -- * Pipes
     -- $pipes
-    map,
-    mapM,
-    filter,
-    filterM,
-    take,
-    takeWhile,
-    drop,
-    dropWhile,
-    concat,
-    elemIndices,
-    findIndices,
-    scan,
-    scanM,
-    chain,
-    read,
-    show,
+    , map
+    , mapM
+    , mapFoldable
+    , filter
+    , filterM
+    , take
+    , takeWhile
+    , drop
+    , dropWhile
+    , concat
+    , elemIndices
+    , findIndices
+    , scan
+    , scanM
+    , chain
+    , read
+    , show
 
     -- * Folds
     -- $folds
-    fold,
-    foldM,
-    all,
-    any,
-    and,
-    or,
-    elem,
-    notElem,
-    find,
-    findIndex,
-    head,
-    index,
-    last,
-    length,
-    maximum,
-    minimum,
-    null,
-    sum,
-    product,
-    toList,
-    toListM,
+    , fold
+    , foldM
+    , all
+    , any
+    , and
+    , or
+    , elem
+    , notElem
+    , find
+    , findIndex
+    , head
+    , index
+    , last
+    , length
+    , maximum
+    , minimum
+    , null
+    , sum
+    , product
+    , toList
+    , toListM
 
     -- * Zips
-    zip,
-    zipWith,
-
+    , zip
+    , zipWith
+#ifndef haskell98
     -- * Utilities
-    tee,
-    generalize
+    , tee
+    , generalize
+#endif
     ) where
 
 import Control.Exception (throwIO, try)
 import Control.Monad (liftM, replicateM_, when, unless)
-import Control.Monad.Trans.State.Strict (get, put)
 import Data.Functor.Identity (Identity, runIdentity)
 import Data.Void (absurd)
 import Foreign.C.Error (Errno(Errno), ePIPE)
 import qualified GHC.IO.Exception as G
 import Pipes
-import Pipes.Core
 import Pipes.Internal
-import Pipes.Lift (evalStateP)
 import qualified System.IO as IO
 import qualified Prelude
+#ifndef haskell98
+import Control.Monad.Trans.State.Strict (get, put)
+import Pipes.Core
+import Pipes.Lift (evalStateP)
+#endif
 import Prelude hiding (
-    all,
-    and,
-    any,
-    concat,
-    drop,
-    dropWhile,
-    elem,
-    filter,
-    head,
-    last,
-    length,
-    map,
-    mapM,
-    maximum,
-    minimum,
-    notElem,
-    null,
-    or,
-    print,
-    product,
-    read,
-    readLn,
-    show,
-    sum,
-    take,
-    takeWhile,
-    zip,
-    zipWith )
+      all
+    , and
+    , any
+    , concat
+    , drop
+    , dropWhile
+    , elem
+    , filter
+    , head
+    , last
+    , length
+    , map
+    , mapM
+    , maximum
+    , minimum
+    , notElem
+    , null
+    , or
+    , print
+    , product
+    , read
+    , readLn
+    , show
+    , sum
+    , take
+    , takeWhile
+    , zip
+    , zipWith
+    )
 
 {- $producers
     Use 'for' loops to iterate over 'Producer's whenever you want to perform the
@@ -211,15 +221,25 @@
 {-# INLINABLE stdoutLn #-}
 
 -- | 'print' values to 'IO.stdout'
-print :: (MonadIO m) => (Show a) => Consumer' a m r
-print = for cat (liftIO . Prelude.print)
+print :: (MonadIO m, Show a) => Consumer' a m r
+print = for cat (\a -> liftIO (Prelude.print a))
 {-# INLINABLE print #-}
 
+{-# RULES
+    "p >-> print" forall p .
+        p >-> print = for p (\a -> liftIO (Prelude.print a))
+  #-}
+
 -- | Write 'String's to a 'IO.Handle' using 'IO.hPutStrLn'
 toHandle :: (MonadIO m) => IO.Handle -> Consumer' String m r
-toHandle handle = for cat $ \str -> liftIO (IO.hPutStrLn handle str)
+toHandle handle = for cat (\str -> liftIO (IO.hPutStrLn handle str))
 {-# INLINABLE toHandle #-}
 
+{-# RULES
+    "p >-> toHandle handle" forall p handle .
+        p >-> toHandle handle = for p (\str -> liftIO (IO.hPutStrLn handle str))
+  #-}
+
 {- $pipes
     Use ('>->') to connect 'Producer's, 'Pipe's, and 'Consumer's:
 
@@ -235,9 +255,17 @@
 
 -- | Apply a function to all values flowing downstream
 map :: (Monad m) => (a -> b) -> Pipe a b m r
-map f = for cat (yield . f)
+map f = for cat (\a -> yield (f a))
 {-# INLINABLE map #-}
 
+{-# RULES
+    "p >-> map f" forall p f . p >-> map f = for p (\a -> yield (f a))
+
+  ; "map f >-> p" forall p f . map f >-> p = (do
+        a <- await
+        return (f a) ) >~ p
+  #-}
+
 -- | Apply a monadic function to all values flowing downstream
 mapM :: (Monad m) => (a -> m b) -> Pipe a b m r
 mapM f = for cat $ \a -> do
@@ -245,11 +273,39 @@
     yield b
 {-# INLINABLE mapM #-}
 
+{-# RULES
+    "p >-> mapM f" forall p f . p >-> mapM f = for p (\a -> do
+        b <- lift (f a)
+        yield b )
+
+  ; "mapM f >-> p" forall p f . mapM f >-> p = (do
+        a <- await
+        b <- lift (f a)
+        return b ) >~ p
+  #-}
+
+{- | Apply a function to all values flowing downstream, and
+     forward each element of the result.
+-}
+mapFoldable :: (Monad m, Foldable t) => (a -> t b) -> Pipe a b m r
+mapFoldable f = for cat (\a -> each (f a))
+{-# INLINABLE mapFoldable #-}
+
+{-# RULES
+    "p >-> mapFoldable f" forall p f .
+        p >-> mapFoldable f = for p (\a -> each (f a))
+  #-}
+
 -- | @(filter predicate)@ only forwards values that satisfy the predicate.
 filter :: (Monad m) => (a -> Bool) -> Pipe a a m r
 filter predicate = for cat $ \a -> when (predicate a) (yield a)
 {-# INLINABLE filter #-}
 
+{-# RULES
+    "p >-> filter predicate" forall p predicate.
+        p >-> filter predicate = for p (\a -> when (predicate a) (yield a))
+  #-}
+
 {-| @(filterM predicate)@ only forwards values that satisfy the monadic
     predicate
 -}
@@ -259,6 +315,13 @@
     when b (yield a)
 {-# INLINABLE filterM #-}
 
+{-# RULES
+    "p >-> filterM predicate" forall p predicate .
+        p >-> filterM predicate = for p (\a -> do
+            b <- lift (predicate a)
+            when b (yield a) )
+  #-}
+
 -- | @(take n)@ only allows @n@ values to pass through
 take :: (Monad m) => Int -> Pipe a a m ()
 take n = replicateM_ n $ do
@@ -308,6 +371,10 @@
 concat = for cat each
 {-# INLINABLE concat #-}
 
+{-# RULES
+    "p >-> concat" forall p . p >-> concat = for p each
+  #-}
+
 -- | Outputs the indices of all elements that match the given element
 elemIndices :: (Monad m, Eq a) => a -> Pipe a Int m r
 elemIndices a = findIndices (a ==)
@@ -355,6 +422,18 @@
     yield a
 {-# INLINABLE chain #-}
 
+{-# RULES
+    "p >-> chain f" forall p f .
+        p >-> chain f = for p (\a -> do
+            lift (f a)
+            yield a )
+  ; "chain f >-> p" forall p f .
+        chain f >-> p = (do
+            a <- await
+            lift (f a)
+            return a ) >~ p
+  #-}
+
 -- | Parse 'Read'able values, only forwarding the value if the parse succeeds
 read :: (Monad m, Read a) => Pipe String a m r
 read = for cat $ \str -> case (reads str) of
@@ -362,6 +441,13 @@
     _         -> return ()
 {-# INLINABLE read #-}
 
+{-# RULES
+    "p >-> read" forall p .
+        p >-> read = for p (\str -> case (reads str) of
+            [(a, "")] -> yield a
+            _         -> return () )
+  #-}
+
 -- | Convert 'Show'able values to 'String's
 show :: (Monad m, Show a) => Pipe a String m r
 show = map Prelude.show
@@ -412,14 +498,14 @@
     predicate.
 -}
 all :: (Monad m) => (a -> Bool) -> Producer a m () -> m Bool
-all predicate p = null $ for p $ \a -> when (not $ predicate a) (yield a)
+all predicate p = null $ p >-> filter (\a -> not (predicate a))
 {-# INLINABLE all #-}
 
 {-| @(any predicate p)@ determines whether any element of @p@ satisfies the
     predicate.
 -}
 any :: (Monad m) => (a -> Bool) -> Producer a m () -> m Bool
-any predicate p = liftM not $ null $ for p $ \a -> when (predicate a) (yield a)
+any predicate p = liftM not $ null (p >-> filter predicate)
 {-# INLINABLE any #-}
 
 -- | Determines whether all elements are 'True'
@@ -436,7 +522,7 @@
     otherwise
 -}
 elem :: (Monad m, Eq a) => a -> Producer a m () -> m Bool
-elem a = any (a ==) 
+elem a = any (a ==)
 {-# INLINABLE elem #-}
 
 {-| @(notElem a)@ returns 'False' if @p@ has an element equal to @a@, 'True'
@@ -448,7 +534,7 @@
 
 -- | Find the first element of a 'Producer' that satisfies the predicate
 find :: (Monad m) => (a -> Bool) -> Producer a m () -> m (Maybe a)
-find predicate p = head $ for p  $ \a -> when (predicate a) (yield a)
+find predicate p = head (p >-> filter predicate)
 {-# INLINABLE find #-}
 
 {-| Find the index of the first element of a 'Producer' that satisfies the
@@ -588,6 +674,7 @@
                         go p1' p2'
 {-# INLINABLE zipWith #-}
 
+#ifndef haskell98
 {-| Transform a 'Consumer' to a 'Pipe' that reforwards all values further
     downstream
 -}
@@ -627,3 +714,4 @@
         x <- respond a
         lift $ put x
 {-# INLINABLE generalize #-}
+#endif
diff --git a/src/Pipes/Tutorial.hs b/src/Pipes/Tutorial.hs
--- a/src/Pipes/Tutorial.hs
+++ b/src/Pipes/Tutorial.hs
@@ -1282,7 +1282,7 @@
 
     * 'Effect'': marks both ends unused but still open
 
-> type Effect' a m r = forall x' x y' y . Proxy x' x y' y m r
+> type Effect' m r = forall x' x y' y . Proxy x' x y' y m r
 >
 > Upstream | Downstream
 >     +---------+
@@ -1419,4 +1419,23 @@
     Any time you get type errors like these you can work through them by
     expanding out the type synonyms and seeing which type variables do not
     match.
+
+    You may also consult this table of type synonyms to more easily compare
+    them:
+
+> type Effect             = Proxy Void () () Void
+> type Producer         b = Proxy Void () () b
+> type Consumer    a      = Proxy ()   a  () Void
+> type Pipe        a    b = Proxy ()   a  () b
+>
+> type Server        b' b = Proxy Void () b' b 
+> type Client   a' a      = Proxy a'   a  () Void
+>
+> type Effect'            m r = forall x' x y' y . Proxy x' x y' y m r
+> type Producer'        b m r = forall x' x      . Proxy x' x () b m r
+> type Consumer'   a      m r = forall      y' y . Proxy () a y' y m r
+>
+> type Server'       b' b m r = forall x' x      . Proxy x' x b' b m r
+> type Client'  a' a      m r = forall      y' y . Proxy a' a y' y m r
+
 -}
diff --git a/tests/Main.hs b/tests/Main.hs
new file mode 100644
--- /dev/null
+++ b/tests/Main.hs
@@ -0,0 +1,272 @@
+module Main (main) where
+
+import Data.Function                        (on)
+import Data.List                            (intercalate)
+import Control.Monad                        ((>=>))
+import Control.Monad.Trans.Writer           (Writer, runWriter, tell)
+import Test.QuickCheck                      (Gen, Arbitrary(..), choose)
+import Test.Framework                       (defaultMain, testGroup, Test)
+import Test.Framework.Providers.QuickCheck2 (testProperty)
+
+import Pipes
+import Pipes.Core
+import Prelude hiding (log)
+
+
+main :: IO ()
+main = defaultMain tests
+
+tests :: [Test]
+tests =
+    [ testGroup "Kleisli Category"        $ testCategory (>=>) return
+    , testGroup "Respond Category"        $ testCategory (/>/) respond
+     ++ [ testProperty "Distributivity" prop_respond_Distributivity
+        ]
+    , testGroup "Request Category"        $ testCategory (\>\) request
+     ++ [ testProperty "Distributivity" prop_request_Distributivity
+        , testProperty "Zero Law"       prop_request_ZeroLaw
+        ]
+    , testGroup "Pull Category"           $ testCategory (>+>) pull
+    , testGroup "Push Category"           $ testCategory (>~>) push
+    , testGroup "Push/Pull"
+        [ testProperty "Associativity"  prop_pushPull_Associativity
+        ]
+    , testGroup "Duals"
+        [ testGroup "Request"
+            [ testProperty "Composition" prop_dual_RequestComposition
+            , testProperty "Identity"    prop_dual_RequestIdentity
+            ]
+        , testGroup "Respond"
+            [ testProperty "Composition" prop_dual_RespondComposition
+            , testProperty "Identity"    prop_dual_RespondIdentity
+            ]
+        , testProperty "Distributivity"  prop_dual_ReflectDistributivity
+        , testProperty "Zero Law"        prop_dual_ReflectZeroLaw
+        , testProperty "Involution"      prop_dual_Involution
+        ]
+    , testGroup "Functor Laws"
+        [ testProperty "Identity"        prop_FunctorIdentity
+        ]
+    ]
+
+arbitraryBoundedEnum' :: (Bounded a, Enum a) => Gen a
+arbitraryBoundedEnum' =
+  do let mn = minBound
+         mx = maxBound `asTypeOf` mn
+     n <- choose (fromEnum mn, fromEnum mx)
+     return (toEnum n `asTypeOf` mn)
+
+data ClientStep
+    = ClientRequest
+    | ClientLog
+    | ClientInc
+      deriving (Enum, Bounded)
+
+instance Arbitrary ClientStep where
+    arbitrary = arbitraryBoundedEnum'
+    shrink _  = []
+
+instance Show ClientStep where
+    show x = case x of
+        ClientRequest -> "request"
+        ClientLog     -> "log"
+        ClientInc     -> "inc"
+
+data ServerStep
+    = ServerRespond
+    | ServerLog
+    | ServerInc
+      deriving (Enum, Bounded)
+
+instance Arbitrary ServerStep where
+    arbitrary = arbitraryBoundedEnum'
+    shrink _  = []
+
+instance Show ServerStep where
+    show x = case x of
+        ServerRespond -> "respond"
+        ServerLog     -> "log"
+        ServerInc     -> "inc"
+
+data ProxyStep
+    = ProxyRequest
+    | ProxyRespond
+    | ProxyLog
+    | ProxyInc deriving (Enum, Bounded)
+
+instance Arbitrary ProxyStep where
+    arbitrary = arbitraryBoundedEnum'
+    shrink _  = []
+
+instance Show ProxyStep where
+    show x = case x of
+        ProxyRequest -> "request"
+        ProxyRespond -> "respond"
+        ProxyLog     -> "log"
+        ProxyInc     -> "inc"
+
+log :: Int -> Proxy a' a b' b (Writer [Int]) Int
+log n = do
+    lift (tell [n])
+    return n
+
+inc :: (Monad m) => Int -> Proxy a' a b' b m Int
+inc n = return (n + 1)
+
+correct :: String -> String
+correct str = case str of
+    [] -> "return"
+    _  -> str
+
+newtype AClient = AClient { unAClient :: [ClientStep] }
+
+instance Arbitrary AClient where
+    arbitrary = fmap AClient arbitrary
+    shrink    = map AClient . shrink . unAClient
+
+instance Show AClient where
+    show = correct . intercalate " >=> " . map show . unAClient
+
+aClient :: AClient -> Int -> Client Int Int (Writer [Int]) Int
+aClient = foldr (>=>) return . map f . unAClient
+  where
+    f x = case x of
+        ClientRequest -> request
+        ClientLog     -> log
+        ClientInc     -> inc
+
+newtype AServer = AServer { unAServer :: [ServerStep] }
+
+instance Arbitrary AServer where
+    arbitrary = fmap AServer arbitrary
+    shrink    = map AServer . shrink . unAServer
+
+instance Show AServer where
+    show = correct . intercalate " >=> " . map show . unAServer
+
+aServer :: AServer -> Int -> Server Int Int (Writer [Int]) Int
+aServer = foldr (>=>) return . map f . unAServer
+  where
+    f x = case x of
+        ServerRespond -> respond
+        ServerLog     -> log
+        ServerInc     -> inc
+
+newtype AProxy = AProxy { unAProxy :: [ProxyStep] }
+
+instance Arbitrary AProxy where
+    arbitrary = fmap AProxy arbitrary
+    shrink    = map AProxy . shrink . unAProxy
+
+instance Show AProxy where
+    show = correct . intercalate " >=> " . map show . unAProxy
+
+aProxy :: AProxy -> Int -> Proxy Int Int Int Int (Writer [Int]) Int
+aProxy = foldr (>=>) return . map f . unAProxy
+  where
+    f x = case x of
+        ProxyRequest -> request
+        ProxyRespond -> respond
+        ProxyLog     -> log
+        ProxyInc     -> inc
+
+type ProxyK    = Int -> Proxy Int Int Int Int (Writer [Int]) Int
+type Operation = ProxyK -> ProxyK -> ProxyK
+
+infix 0 ===
+
+(===) :: ProxyK -> ProxyK -> AServer -> AClient -> Bool
+(===) pl pr p0 p1 =
+  let sv  = aServer p0
+      cl  = aClient p1
+      f p = runWriter (runEffect (p 0))
+  in on (==) f (sv >+> pl >+> cl) (sv >+> pr >+> cl)
+
+gen_prop_RightIdentity, gen_prop_LeftIdentity
+    :: Operation
+    -> ProxyK -- right/left identity element
+    -> AProxy -> AServer -> AClient -> Bool
+gen_prop_RightIdentity (>>>) idt f' =
+    let f = aProxy  f'
+    in (f >>> idt) === f
+
+gen_prop_LeftIdentity (>>>) idt f' =
+    let f = aProxy f'
+    in (idt >>> f) === f
+
+gen_prop_Associativity
+    :: Operation
+    -> AProxy -> AProxy -> AProxy -> AServer -> AClient -> Bool
+gen_prop_Associativity (>>>) f' g' h' =
+    let f = aProxy  f'
+        g = aProxy  g'
+        h = aProxy  h'
+    in f >>> (g >>> h) === (f >>> g) >>> h
+
+testCategory :: Operation -> ProxyK -> [Test]
+testCategory op idt =
+    [ testProperty "Left Identity"  $ gen_prop_LeftIdentity  op idt
+    , testProperty "Right Identity" $ gen_prop_RightIdentity op idt
+    , testProperty "Associativity"  $ gen_prop_Associativity op
+    ]
+
+-- Respond Category
+
+prop_respond_Distributivity f' g' h' =
+    let f = aProxy  f'
+        g = aProxy  g'
+        h = aProxy  h'
+    in (f >=> g) />/ h === (f />/ h) >=> (g />/ h)
+
+-- Request Category
+
+prop_request_Distributivity f' g' h' =
+    let f = aProxy  f'
+        g = aProxy  g'
+        h = aProxy  h'
+    in f \>\ (g >=> h) === (f \>\ g) >=> (f \>\ h)
+
+prop_request_ZeroLaw f' =
+    let f = aProxy  f'
+    in (f \>\ return) === return
+
+-- Push/Pull
+
+prop_pushPull_Associativity f' g' h' =
+    let f = aProxy f'
+        g = aProxy g'
+        h = aProxy h'
+    in (f >+> g) >~> h === f >+> (g >~> h)
+
+-- Duals
+
+prop_dual_RequestComposition f' g' =
+    let f = aProxy f'
+        g = aProxy g'
+    in reflect . (f \>\ g) === reflect . g />/ reflect . f
+
+prop_dual_RequestIdentity = reflect . request === respond
+
+prop_dual_RespondComposition f' g' =
+    let f = aProxy f'
+        g = aProxy g'
+    in  reflect . (f />/ g) === reflect . g \>\ reflect . f
+
+prop_dual_RespondIdentity = reflect . respond === request
+
+prop_dual_ReflectDistributivity f' g' =
+    let f = aProxy f'
+        g = aProxy g'
+    in reflect . (f >=> g) === reflect . f >=> reflect . g
+
+prop_dual_ReflectZeroLaw = reflect . return === return
+
+prop_dual_Involution f' =
+    let f = aProxy f'
+    in (reflect . reflect) . f >=> return === f
+
+-- Functor Laws
+
+prop_FunctorIdentity p' =
+    let p = aProxy p'
+    in fmap id p === id p
