diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+Copyright (c) 2023 John Ky
+Copyright (c) 2019 Tom Harding
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/oops-examples.cabal b/oops-examples.cabal
new file mode 100644
--- /dev/null
+++ b/oops-examples.cabal
@@ -0,0 +1,45 @@
+cabal-version: 3.0
+
+name:                   oops-examples
+version:                0.2.0.0
+synopsis:               Oops examples
+description:            Oops examples.
+homepage:               https://www.github.com/haskell-works/oops
+license:                MIT
+license-file:           LICENSE
+author:                 John Ky
+maintainer:             newhoggy@gmail.com
+copyright:              2023 John Ky
+                        2019 Tom Harding
+category:               Data
+                        Control
+tested-with:            GHC == 9.4.4, GHC == 9.2.5, GHC == 9.0.2, GHC == 8.10.7
+
+source-repository head
+  type: git
+  location: https://github.com/haskell-works/oops
+
+common base                           { build-depends: base                           >= 4.12       && < 5      }
+
+common exceptions                     { build-depends: exceptions                                               }
+common mtl                            { build-depends: mtl                                                      }
+common oops                           { build-depends: oops                                                     }
+common text                           { build-depends: text                                                     }
+common transformers                   { build-depends: transformers                                             }
+
+common project-config
+  default-language:     Haskell2010
+  default-extensions:   BlockArguments
+                        RankNTypes
+                        ScopedTypeVariables
+  ghc-options:          -Wall
+
+library
+  import:               base, project-config,
+                        exceptions,
+                        mtl,
+                        oops,
+                        text,
+                        transformers,
+  exposed-modules:      Examples
+  hs-source-dirs:       src
diff --git a/src/Examples.hs b/src/Examples.hs
new file mode 100644
--- /dev/null
+++ b/src/Examples.hs
@@ -0,0 +1,329 @@
+{-# LANGUAGE TypeApplications #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE TypeOperators #-}
+{-# LANGUAGE MonoLocalBinds #-}
+{-# LANGUAGE DataKinds #-}
+
+module Examples where
+
+import Control.Monad.Except (MonadError, ExceptT, runExceptT)
+import Control.Monad.Oops ( CouldBe, Variant )
+import Data.Text (Text)
+import Text.Read (readMaybe)
+import Data.Functor.Identity (Identity)
+import Control.Monad.Trans (MonadIO)
+import Data.Function ((&))
+
+import qualified Control.Monad.Oops as OO
+import qualified System.IO as IO
+import Control.Monad.IO.Class (MonadIO(..))
+import Control.Exception (IOException)
+import Control.Monad.Catch (MonadCatch)
+
+-- | A simple function that throws an error.
+--
+-- The type is the one that is inferred by GHC.
+readIntV1 :: ()
+  => MonadError (Variant e) m
+  => OO.CouldBeF e Text
+  => String
+  -> m Int
+readIntV1 s = case readMaybe @Int s of
+  Just i -> return i
+  Nothing -> OO.throw @Text "Not an integer"
+
+-- | A simple function that throws an error.
+--
+-- This is the same as before, but we can rewrite constraint on 'e' differently.
+readIntV2 :: ()
+  => MonadError (Variant e) m
+  => e `CouldBe` Text
+  => String
+  -> m Int
+readIntV2 s = case readMaybe @Int s of
+  Just i -> return i
+  Nothing -> OO.throw @Text "Not an integer"
+
+-- | A simple function that throws an error.
+--
+-- We can also use 'ExceptT'
+readIntV3 :: ()
+  => e `CouldBe` Text
+  => String
+  -> ExceptT (Variant e) Identity Int
+readIntV3 s = case readMaybe @Int s of
+  Just i -> return i
+  Nothing -> OO.throw @Text "Not an integer"
+
+-- | A simple IO function that throws an error.
+--
+-- We can also use 'ExceptT' of 'IO'.
+readIntV4 :: ()
+  => e `CouldBe` Text
+  => String
+  -> ExceptT (Variant e) IO Int
+readIntV4 s = case readMaybe @Int s of
+  Just i -> return i
+  Nothing -> OO.throw @Text "Not an integer"
+
+-- | A simple function that throws an error.
+--
+-- Or use MonadIO instead of IO directly.
+readIntV5 :: ()
+  => MonadError (Variant e) m
+  => MonadIO m
+  => e `CouldBe` Text
+  => String
+  -> m Int
+readIntV5 s = case readMaybe @Int s of
+  Just i -> return i
+  Nothing -> OO.throw @Text "Not an integer"
+
+-- | A simple function that throws an error.
+--
+-- Or use 'MonadIO' with 'ExceptT'.
+readIntV6 :: ()
+  => MonadIO m
+  => e `CouldBe` Text
+  => String
+  -> ExceptT (Variant e) m Int
+readIntV6 s = case readMaybe @Int s of
+  Just i -> return i
+  Nothing -> OO.throw @Text "Not an integer"
+
+-- We can represent each error as a separate type.
+
+data NotAnInteger = NotAnInteger
+
+data NotPositive = NotPositive
+
+-- | A simple function can throw two errors
+readPositiveInt1 :: ()
+  => MonadIO m
+  => e `CouldBe` NotAnInteger
+  => e `CouldBe` NotPositive
+  => String
+  -> ExceptT (Variant e) m Int
+readPositiveInt1 s =
+  case readMaybe @Int s of
+    Just i ->
+      if i > 0
+        then return i
+        else OO.throw NotPositive
+    Nothing -> OO.throw NotAnInteger
+
+-- We can call a function that throws an error of type 'Text' and allow the
+-- error to propagate by declaring we also throw an error of type 'Text'.
+example1 :: ()
+  => e `CouldBe` Text
+  => String
+  -> ExceptT (Variant e) IO Int
+example1 s = do
+  i <- readInt s
+  liftIO $ IO.print i
+  return i
+  where
+    readInt :: ()
+      => e `CouldBe` Text
+      => String
+      -> ExceptT (Variant e) IO Int
+    readInt = error "unimplemented"
+
+-- Or alternatively, we can catch the error of type 'Text' and handle it.
+-- in which case the error doesn't propagate.  Notice the 'e CouldBe Text'
+-- constraint is not needed in this case.
+example2 :: ()
+  => String
+  -> ExceptT (Variant e) IO Int
+example2 s = do
+  i <- readInt s
+    & OO.catch @Text (\_ -> pure 0)
+  liftIO $ IO.print i
+  return i
+  where
+    readInt :: ()
+      => e `CouldBe` Text
+      => String
+      -> ExceptT (Variant e) IO Int
+    readInt = error "unimplemented"
+
+-- When we don't throw any errors, we can use the 'runOops' function to
+-- convert the 'ExceptT' to an 'IO' action.
+example3 :: ()
+  => String
+  -> IO Int
+example3 s = OO.runOops $ do
+  i <- readInt s
+    & OO.catch @Text (\_ -> pure 0)
+  liftIO $ IO.print i
+  return i
+  where
+    readInt :: ()
+      => e `CouldBe` Text
+      => String
+      -> ExceptT (Variant e) IO Int
+    readInt = error "unimplemented"
+
+data FileNotFound = FileNotFound
+
+data FileNotReadable = FileNotReadable
+
+data Errors1
+  = Errors1NotPositive NotPositive
+  | Errors1NotAnInteger NotAnInteger
+
+-- We can call a function that throws multiple errors into a function
+-- that only throws one by just catching and rethrowing the one
+-- error.
+example4 :: ()
+  => MonadIO m
+  => e `CouldBe` Errors1
+  => String
+  -> ExceptT (Variant e) m Int
+example4 s = do
+  i <- readPositiveInt1 s
+    & OO.catch @NotPositive (OO.throw . Errors1NotPositive)
+    & OO.catch @NotAnInteger (OO.throw . Errors1NotAnInteger)
+  liftIO $ IO.print i
+  return i
+
+--------------------------------------------------------------------------------
+-- Embedding 'Oops' into vanilla 'ExceptT' and 'Either' code.
+
+-- We we have a function that only throws one error, we can use 'runOopsInExceptT'
+-- to remove the 'Variant' wrapper leaving only the 'ExceptT'.
+example5 :: ()
+  => MonadIO m
+  => String
+  -> ExceptT Errors1 m Int
+example5 s = OO.runOopsInExceptT $ do
+  i <- readPositiveInt1 s
+    & OO.catch @NotPositive (OO.throw . Errors1NotPositive)
+    & OO.catch @NotAnInteger (OO.throw . Errors1NotAnInteger)
+  liftIO $ IO.print i
+  return i
+
+-- We we have a function that only throws one error, we can use 'runOopsInEither'
+-- to remove the 'Variant' wrapper and the 'Except' leaving only the 'Either'.
+example6 :: ()
+  => MonadIO m
+  => String
+  -> m (Either Errors1 Int)
+example6 s = OO.runOopsInEither $ do
+  i <- readPositiveInt1 s
+    & OO.catch @NotPositive (OO.throw . Errors1NotPositive)
+    & OO.catch @NotAnInteger (OO.throw . Errors1NotAnInteger)
+  liftIO $ IO.print i
+  return i
+
+--------------------------------------------------------------------------------
+-- Embedding vanilla 'ExceptT' and 'Either' code into 'Oops' code.
+-- We can call a function that throws multiple errors into a function
+-- that only throws one by just catching and rethrowing the one
+-- error.
+
+-- We can call a function that throws vanilla 'ExceptT' errors and use 'onLeft'
+-- to catch and rethrow the errors as oops errors.
+example7 :: ()
+  => MonadIO m
+  => e `CouldBe` NotPositive
+  => e `CouldBe` NotAnInteger
+  => String
+  -> ExceptT (Variant e) m Int
+example7 s = do
+  i <- readInt s
+    & OO.onLeft OO.throw
+  pos <- requirePositive i
+    & OO.onLeft OO.throw
+  liftIO $ IO.print pos
+  return pos
+  where
+    readInt :: MonadIO m => String -> m (Either NotAnInteger Int)
+    readInt = error "unimplemented"
+    requirePositive :: MonadIO m => Int -> m (Either NotPositive Int)
+    requirePositive = error "unimplemented"
+
+-- 'onLeftThrow' is shorthand for 'onLeft throw'.
+example8 :: ()
+  => MonadIO m
+  => e `CouldBe` NotPositive
+  => e `CouldBe` NotAnInteger
+  => String
+  -> ExceptT (Variant e) m Int
+example8 s = do
+  i <- readInt s
+    & OO.onLeftThrow
+  pos <- requirePositive i
+    & OO.onLeftThrow
+  liftIO $ IO.print pos
+  return pos
+  where
+    readInt :: MonadIO m => String -> m (Either NotAnInteger Int)
+    readInt = error "unimplemented"
+    requirePositive :: MonadIO m => Int -> m (Either NotPositive Int)
+    requirePositive = error "unimplemented"
+
+-- We can similarly call a function that throws via vanilla 'ExceptT'
+example9 :: ()
+  => MonadIO m
+  => e `CouldBe` NotPositive
+  => e `CouldBe` NotAnInteger
+  => String
+  -> ExceptT (Variant e) m Int
+example9 s = do
+  i <- liftIO (runExceptT (readInt s))
+    & OO.onLeftThrow
+  pos <- liftIO (runExceptT (requirePositive i))
+    & OO.onLeftThrow
+  liftIO $ IO.print pos
+  return pos
+  where
+    readInt :: String -> ExceptT NotAnInteger IO Int
+    readInt = error "unimplemented"
+    requirePositive :: Int -> ExceptT NotPositive IO Int
+    requirePositive = error "unimplemented"
+
+-- We can similarly call a function that throws via vanilla 'ExceptT'
+example10 :: ()
+  => MonadIO m
+  => e `CouldBe` NotPositive
+  => e `CouldBe` NotAnInteger
+  => String
+  -> ExceptT (Variant e) m Int
+example10 s = do
+  i <- liftIO (runExceptT (readInt s))
+    & OO.onLeftThrow
+  pos <- liftIO (runExceptT (requirePositive i))
+    & OO.onLeftThrow
+  liftIO $ IO.print pos
+  return pos
+  where
+    readInt :: String -> ExceptT NotAnInteger IO Int
+    readInt = error "unimplemented"
+    requirePositive :: Int -> ExceptT NotPositive IO Int
+    requirePositive = error "unimplemented"
+
+-- We can also catch runtime exceptions and rethrow them as checked exceptions.
+example11 :: ()
+  => MonadIO m
+  => MonadCatch m
+  => e `CouldBe` IOException
+  => ExceptT (Variant e) m String
+example11 = do
+  i <- liftIO (IO.readFile "moo")
+    & OO.onException @IOException OO.throw
+  liftIO $ IO.print i
+  return i
+
+-- The 'onExceptionThrow' is shorthand for 'onException throw'.
+example12 :: ()
+  => MonadIO m
+  => MonadCatch m
+  => e `CouldBe` IOException
+  => ExceptT (Variant e) m String
+example12 = do
+  i <- liftIO (IO.readFile "moo")
+    & OO.onExceptionThrow @IOException
+  liftIO $ IO.print i
+  return i
