diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,3 +1,13 @@
-# Changelog for too-fast-too-free
+# Changelog for polysemy
+
+## 0.1.1.0 (2019-04-14)
+
+- Added 'runIO' interpretation (thanks to @adamConnerSax)
+- Minor documentation fixes
+
+
+## 0.1.0.0 (2019-04-11)
+
+- Initial release
 
 ## Unreleased changes
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -22,7 +22,7 @@
 It's like `mtl` but composes better, requires less boilerplate, and avoids the
 O(n^2) instances problem.
 
-It's like `freer-simple` but more powerful and 700x faster.
+It's like `freer-simple` but more powerful and 35x faster.
 
 It's like `fused-effects` but with an order of magnitude less boilerplate.
 
@@ -47,7 +47,7 @@
 Console effect:
 
 ```haskell
-{-# LANGAUGE TemplateHaskell #-}
+{-# LANGUAGE TemplateHaskell #-}
 
 import Polysemy
 
diff --git a/bench/Poly.hs b/bench/Poly.hs
--- a/bench/Poly.hs
+++ b/bench/Poly.hs
@@ -1,6 +1,8 @@
+{-# LANGUAGE BlockArguments   #-}
 {-# LANGUAGE DataKinds        #-}
 {-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE GADTs            #-}
+{-# LANGUAGE TemplateHaskell  #-}
 {-# LANGUAGE TypeApplications #-}
 
 {-# OPTIONS_GHC -fwarn-all-missed-specializations #-}
@@ -11,6 +13,8 @@
 import Polysemy.Error
 import Polysemy.Resource
 import Polysemy.State
+import Polysemy.Input
+import Polysemy.Output
 
 
 slowBeforeSpecialization :: Member (State Int) r => Semantic r Int
@@ -42,4 +46,18 @@
        . (runM .@ runResource .@@ runErrorInIO)
        . runState False
        $ prog
+
+data Console m a where
+  ReadLine :: Console m String
+  WriteLine :: String -> Console m ()
+
+makeSemantic ''Console
+
+runConsoleBoring :: [String] -> Semantic (Console ': r) a -> Semantic r ([String], a)
+runConsoleBoring inputs
+  = runFoldMapOutput (:[])
+  . runListInput inputs
+  . reinterpret2 \case
+      ReadLine -> maybe "" id <$> input
+      WriteLine msg -> output msg
 
diff --git a/polysemy.cabal b/polysemy.cabal
--- a/polysemy.cabal
+++ b/polysemy.cabal
@@ -1,6 +1,6 @@
 cabal-version: 1.12
 name: polysemy
-version: 0.1.0.0
+version: 0.1.1.0
 license: BSD3
 license-file: LICENSE
 copyright: 2019 Sandy Maguire
@@ -49,6 +49,7 @@
         Polysemy.Internal.TH.Effect
         Polysemy.Internal.TH.Performance
         Polysemy.Internal.Union
+        Polysemy.IO
         Polysemy.NonDet
         Polysemy.Output
         Polysemy.Random
diff --git a/src/Polysemy.hs b/src/Polysemy.hs
--- a/src/Polysemy.hs
+++ b/src/Polysemy.hs
@@ -8,17 +8,17 @@
   , runM
 
   -- * Interoperating With Other Monads
-  , Lift ()
+  , Lift (..)
   , sendM
 
     -- * Lifting
   , raise
 
     -- * Creating New Effects
-    -- | Effects should be defined as a GADT (enable @-XGADTs@), with kind @(* -> *) -> *@.
-    -- Every primitive action in the effect should be its own constructor of
-    -- the type. For example, we can model an effect which interacts with a tty
-    -- console as follows:
+    -- | Effects should be defined as a GADT (enable @-XGADTs@), with kind @(*
+    -- -> *) -> * -> *@. Every primitive action in the effect should be its
+    -- own constructor of the type. For example, we can model an effect which
+    -- interacts with a tty console as follows:
     --
     -- @
     -- data Console m a where
diff --git a/src/Polysemy/IO.hs b/src/Polysemy/IO.hs
new file mode 100644
--- /dev/null
+++ b/src/Polysemy/IO.hs
@@ -0,0 +1,41 @@
+{-# LANGUAGE AllowAmbiguousTypes #-}
+
+module Polysemy.IO
+  ( -- * Interpretations
+    runIO
+  ) where
+
+import Polysemy
+import Control.Monad.IO.Class
+
+
+------------------------------------------------------------------------------
+-- | The 'MonadIO' class is conceptually an interpretation of 'IO' to some
+-- other monad. This function reifies that intuition, by transforming an 'IO'
+-- effect into some other 'MonadIO'.
+--
+-- This function is especially useful when using the 'MonadIO' instance for
+-- 'Semantic' instance.
+--
+-- Make sure to type-apply the desired 'MonadIO' instance when using 'runIO'.
+--
+-- @since 0.1.1.0
+--
+-- ==== Example
+--
+-- @
+-- foo :: PandocIO ()
+-- foo = 'runM' . 'runIO' @PandocIO $ do
+--   'liftIO' $ putStrLn "hello from polysemy"
+-- @
+--
+runIO
+    :: forall m r a
+     . ( MonadIO m
+       , Member (Lift m) r
+       )
+    => Semantic (Lift IO ': r) a
+    -> Semantic r a
+runIO = interpret $ sendM . liftIO @m . unLift
+{-# INLINE runIO #-}
+
diff --git a/src/Polysemy/Internal.hs b/src/Polysemy/Internal.hs
--- a/src/Polysemy/Internal.hs
+++ b/src/Polysemy/Internal.hs
@@ -11,7 +11,7 @@
   , run
   , runM
   , raise
-  , Lift ()
+  , Lift (..)
   , usingSemantic
   , liftSemantic
   , hoistSemantic
@@ -63,7 +63,7 @@
 -- monomorphic representation of the @r@ parameter.
 --
 -- After all of your effects are handled, you'll be left with either
--- a @'Semantic' '[] a@ or a @'Semantic' ('Lift' m) a@ value, which can be
+-- a @'Semantic' '[] a@ or a @'Semantic' '[ 'Lift' m ] a@ value, which can be
 -- consumed respectively by 'run' and 'runM'.
 --
 -- ==== Examples
@@ -162,18 +162,25 @@
 
 instance (Member NonDet r) => Alternative (Semantic r) where
   empty = send Empty
+  {-# INLINE empty #-}
   a <|> b = do
     send (Choose id) >>= \case
       False -> a
       True  -> b
+  {-# INLINE (<|>) #-}
 
 
+------------------------------------------------------------------------------
+-- | This instance will only lift 'IO' actions. If you want to lift into some
+-- other 'MonadIO' type, use this instance, and handle it via the
+-- 'Polysemy.IO.runIO' interpretation.
 instance (Member (Lift IO) r) => MonadIO (Semantic r) where
   liftIO = sendM
   {-# INLINE liftIO #-}
 
 instance Member Fixpoint r => MonadFix (Semantic r) where
   mfix f = send $ Fixpoint f
+  {-# INLINE mfix #-}
 
 
 liftSemantic :: Union r (Semantic r) a -> Semantic r a
diff --git a/src/Polysemy/Internal/Combinators.hs b/src/Polysemy/Internal/Combinators.hs
--- a/src/Polysemy/Internal/Combinators.hs
+++ b/src/Polysemy/Internal/Combinators.hs
@@ -219,7 +219,7 @@
 ------------------------------------------------------------------------------
 -- | Like 'reinterpret', but introduces /three/ intermediary effects.
 reinterpret3
-    :: FirstOrder e1 "reinterpret2"
+    :: FirstOrder e1 "reinterpret3"
     => (∀ m x. e1 m x -> Semantic (e2 ': e3 ': e4 ': r) x)
        -- ^ A natural transformation from the handled effect to the new effects.
     -> Semantic (e1 ': r) a
diff --git a/src/Polysemy/Internal/Lift.hs b/src/Polysemy/Internal/Lift.hs
--- a/src/Polysemy/Internal/Lift.hs
+++ b/src/Polysemy/Internal/Lift.hs
@@ -23,7 +23,6 @@
 --
 -- Consider using 'Polysemy.Trace.trace' and 'Polysemy.Trace.runTraceIO' as
 -- a substitute for using 'putStrLn' directly.
-newtype Lift m (z :: * -> *) a = Lift
-  { unLift :: m a
-  }
+newtype Lift m (z :: * -> *) a where
+  Lift :: { unLift :: m a } -> Lift m z a
 
diff --git a/src/Polysemy/Internal/Tactics.hs b/src/Polysemy/Internal/Tactics.hs
--- a/src/Polysemy/Internal/Tactics.hs
+++ b/src/Polysemy/Internal/Tactics.hs
@@ -68,7 +68,7 @@
 -- block will not be visible inside of the @dealloc@ block.
 --
 -- Power users may explicitly use 'getInitialStateT' and 'bindT' to construct
--- whatever data flow they'd like; although this is usually necessary.
+-- whatever data flow they'd like; although this is usually unnecessary.
 type Tactical e m r x = ∀ f. (Functor f, Typeable1 f)
                           => Semantic (WithTactics e f m r) (f x)
 
