diff --git a/LICENSE b/LICENSE
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
 The MIT License (MIT)
 
-Copyright (c) 2016 Daniel Mendler
+Copyright (c) 2016-2017 Daniel Mendler
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# Intro: My current Haskell Prelude
+# Intro: Safe and minimal Haskell Prelude
 
 [![Hackage](https://img.shields.io/hackage/v/intro.svg)](https://hackage.haskell.org/package/intro)
 [![Build Status](https://secure.travis-ci.org/minad/intro.png?branch=master)](http://travis-ci.org/minad/intro)
@@ -9,22 +9,22 @@
 For String overloading the extension 'OverloadedStrings' should be used.
 Container types and Monad transformers are provided.
 
-Most important - this Prelude tries to keep things simple.
+Most important - this Prelude tries to keep things simple and minimal.
 This means it just reexports from base and commonly used libraries
 and adds only very few additional functions.
 
 List of design decisions:
 
-* Keep everything at one place (There are one two modules and Intro.Trustworthy is only there for Safe Haskell)
+* Keep everything at one place
 * Conservative extension over the base Prelude
-* Rely only on very common external libraries
+* Rely only on common additional libraries
 * Avoid writing custom functions
-* Export everything explicitly to provide a stable interface and for good documentation
+* Export everything explicitly to provide a stable interface and good documentation
 * Export only total functions or provide safe alternatives (Very few exceptions like div etc.)
-* Prefer Text over String, provide ConvertibleStrings
-* Provide Monad transformers
+* Prefer Text over String, provide ConvertString and EncodeString
+* Provide monad transformers
 * Provide container types
 * Prefer generic functions
 * Debugging functions, like 'Intro.Trustworthy.trace' and 'undefined' are available but produce compile time warnings
-* Don't provide error, only panic instead
-* Compatibility with Control.Lens
+* Replace error with panic
+* Compatibility with unqualified import of Control.Lens and Optics
diff --git a/intro.cabal b/intro.cabal
--- a/intro.cabal
+++ b/intro.cabal
@@ -1,10 +1,7 @@
--- This file has been generated from package.yaml by hpack version 0.15.0.
---
--- see: https://github.com/sol/hpack
-
+cabal-version: 1.12
 name:           intro
-version:        0.1.0.10
-synopsis:       "Fixed Prelude" - Mostly total and safe, provides Text and Monad transformers
+version:        0.9.0.0
+synopsis:       Safe and minimal prelude
 description:    Intro is a modern Prelude which provides safe alternatives
                 for most of the partial functions and follows other
                 best practices, e.g., Text is preferred over String.
@@ -22,13 +19,11 @@
 bug-reports:    https://github.com/minad/intro/issues
 author:         Daniel Mendler <mail@daniel-mendler.de>
 maintainer:     Daniel Mendler <mail@daniel-mendler.de>
-copyright:      2016 Daniel Mendler
+copyright:      2016-2017 Daniel Mendler
 license:        MIT
 license-file:   LICENSE
-tested-with:    GHC == 7.10.3, GHC == 8.0.1
+tested-with:    GHC == 8.6.5, GHC == 8.8.1, GHC == 8.10.1
 build-type:     Simple
-cabal-version:  >= 1.10
-
 extra-source-files:
     README.md
 
@@ -37,67 +32,53 @@
   location: https://github.com/minad/intro
 
 library
-  hs-source-dirs:
-      src
-  ghc-options: -Wall
-  build-depends:
-      base                 >= 4.8     && < 5.0
-    , bifunctors           >= 5.2     && < 5.5
-    , binary               >= 0.7     && < 0.9
-    , bytestring           >= 0.9     && < 0.11
-    , containers           >= 0.5     && < 0.6
-    , deepseq              >= 1.4     && < 1.5
-    , dlist                >= 0.7     && < 0.9
-    , extra                >= 1.5.1   && < 1.6
-    , hashable             >= 1.2.5   && < 1.3
-    , mtl                  >= 2.2     && < 2.3
-    , safe                 >= 0.3.11  && < 0.4
-    , string-conversions   >= 0.4     && < 0.5
-    , tagged               >= 0.8     && < 0.9
-    , text                 >= 0.7     && < 1.3
-    , transformers         >= 0.4     && < 0.6
-    , unordered-containers >= 0.2     && < 0.3
-    , writer-cps-mtl       >= 0.1.1.2 && < 0.2
-  if impl(ghc < 8.0)
-    build-depends:
-        semigroups >= 0.9 && < 1
   exposed-modules:
       Intro
   other-modules:
+      Intro.ConvertString
+      Intro.ConvertIntegral
       Intro.Trustworthy
-      Paths_intro
+  hs-source-dirs:
+      src
+  ghc-options: -Wall
+  build-depends:
+      base >=4.8 && <5.0
+    , bytestring >=0.9 && <0.11
+    , containers >=0.5 && <0.7
+    , extra >=1.5.1 && <2.0
+    , hashable >=1.2.5 && <2.0
+    , mtl >=2.2 && <2.3
+    , safe >=0.3.18 && <0.4
+    , text >=0.7 && <1.3
+    , transformers >=0.4 && <0.6
+    , unordered-containers >=0.2 && <0.3
+    , writer-cps-mtl >=0.1.1.2 && <0.2
   default-language: Haskell2010
 
-test-suite compat
+test-suite test
   type: exitcode-stdio-1.0
-  main-is: compat.hs
+  main-is: test.hs
+  other-modules:
+      BaseCompat
+      LensCompat
+      OpticsCompat
   hs-source-dirs:
       test
   ghc-options: -Wall
   build-depends:
-      base                 >= 4.8     && < 5.0
-    , bifunctors           >= 5.2     && < 5.5
-    , binary               >= 0.7     && < 0.9
-    , bytestring           >= 0.9     && < 0.11
-    , containers           >= 0.5     && < 0.6
-    , deepseq              >= 1.4     && < 1.5
-    , dlist                >= 0.7     && < 0.9
-    , extra                >= 1.5.1   && < 1.6
-    , hashable             >= 1.2.5   && < 1.3
-    , mtl                  >= 2.2     && < 2.3
-    , safe                 >= 0.3.11  && < 0.4
-    , string-conversions   >= 0.4     && < 0.5
-    , tagged               >= 0.8     && < 0.9
-    , text                 >= 0.7     && < 1.3
-    , transformers         >= 0.4     && < 0.6
-    , unordered-containers >= 0.2     && < 0.3
-    , writer-cps-mtl       >= 0.1.1.2 && < 0.2
+      QuickCheck
+    , base
+    , bytestring
+    , containers
+    , extra
+    , hashable
     , intro
     , lens
-  if impl(ghc < 8.0)
-    build-depends:
-        semigroups >= 0.9 && < 1
-  other-modules:
-      BaseCompat
-      LensCompat
+    , optics
+    , mtl
+    , safe
+    , text
+    , transformers
+    , unordered-containers
+    , writer-cps-mtl
   default-language: Haskell2010
diff --git a/src/Intro.hs b/src/Intro.hs
--- a/src/Intro.hs
+++ b/src/Intro.hs
@@ -1,5 +1,4 @@
 {-# OPTIONS_HADDOCK show-extensions #-}
-{-# LANGUAGE CPP #-}
 {-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE NoImplicitPrelude #-}
 {-# LANGUAGE OverloadedStrings #-}
@@ -9,7 +8,7 @@
 -----------------------------------------------------------------------------
 -- |
 -- Module      :  Intro
--- Copyright   :  (c) Daniel Mendler 2016
+-- Copyright   :  (c) Daniel Mendler 2016-2017
 -- License     :  MIT
 --
 -- Maintainer  :  mail@daniel-mendler.de
@@ -28,19 +27,19 @@
 --
 -- List of design decisions:
 --
--- * Keep everything at one place (There are one two modules and Intro.Trustworthy is only there for Safe Haskell)
+-- * Keep everything at one place (There are only three modules and Intro.Trustworthy is only there for Safe Haskell)
 -- * Conservative extension over the base Prelude
 -- * Rely only on very common external libraries
 -- * Avoid writing custom functions
 -- * Export everything explicitly to provide a stable interface and for good documentation
 -- * Export only total functions or provide safe alternatives (Very few exceptions like div etc.)
--- * Prefer Text over String, provide ConvertibleStrings
+-- * Prefer Text over String, provide 'ConvertString' and 'EncodeString'
 -- * Provide Monad transformers
 -- * Provide container types
 -- * Prefer generic functions
--- * Debugging functions, like 'Intro.Trustworthy.trace' and 'undefined' are available but produce compile time warnings
+-- * Debugging functions, like 'trace' and 'undefined' are available but produce compile time warnings
 -- * Don't provide error, only panic instead
--- * Compatibility with Control.Lens
+-- * Compatibility with unqualified import of Control.Lens
 --
 -- Some 'Prelude' functions are missing from 'Intro'. More general variants are available for the following functions:
 --
@@ -54,13 +53,26 @@
 -- * 'sequence' = 'Control.Applicative.sequenceA'
 -- * 'sequence_' = 'Control.Applicative.sequenceA_'
 --
--- Unsafe functions are not provided. Use the '*May' or '*Def' alternatives instead.
+-- Integral type conversions are more restricted:
 --
--- * 'cycle', 'head', 'tail', 'init', 'last'
--- * 'foldl1', 'foldr1', 'maximum', 'minimum'
--- * 'toEnum', 'pred', 'succ'
--- * 'read' is replaced by 'readMaybe'
+-- * 'toIntegral' is a safer, but more restricted version of 'fromIntegral'
+-- * 'toIntegerSized' is safe and checked
+-- * 'fromIntegralUnsafe' to get the unsafe, overflowing behavior
+-- * 'fromIntegerUnsafe' instead of 'fromInteger'
 --
+-- Unsafe functions are not provided. For example 'read' is replaced by 'readMaybe'.
+-- The unsafe list functions are replaced by their 'NonEmpty' counterparts. Furthermore '*May' and '*Def'
+-- functions are exported from the 'safe' package, e.g., 'headMay'.
+--
+-- * 'cycleMay', 'headMay', 'tailMay', 'initMay', 'lastMay'
+-- * 'toEnumMay', 'predMay', 'succMay'
+--
+-- The 'maximum' and 'minimum' functions have been replaced by variants which
+-- are safe for empty structures.
+--
+-- * 'maximumBound', 'maximumBounded', ...
+-- * 'minimumBound', 'minimumBounded', ...
+--
 -- These functions are not provided for various reasons:
 --
 -- * '!!' is unsafe and /O(n)/. Use a 'Data.Map.Map' instead.
@@ -70,15 +82,16 @@
 -- * 'gcd' and 'lcm' are not commonly used.
 -- * 'error' and 'errorWithoutStackTrace' are not provided. Use 'panic' instead.
 -- * 'ioError' and 'userError' are not provided. Import modules for exception handling separately if needed.
--- * Some 'Read' and 'Show' class functions are not provided. Don't write these instances yourself.
+-- * Some 'Text.Read.Read' and 'Show' class functions are not provided. Don't write these instances yourself.
 --
 -- Additional types and functions:
 --
 -- * 'LText' alias for lazy 'Text'
 -- * 'LByteString' alias for lazy 'ByteString'
 -- * 'fromFoldable' to convert from 'Data.Foldable.Foldable' to an 'IsList' type
--- * 'convertList' to convert between two 'IsList' types. This function can be used instead of the 'toList' function
---   originally provided by the 'IsList' class.
+-- * 'convertList' to convert between two 'IsList' types.
+-- * 'asList' to convert from an 'IsList' type to a 'List'. This function is an
+--   alias for the 'toList' function from the 'IsList' class.
 -- * 'showT' and 'showS' are monomorphic 'show' functions.
 -- * '<>^' lifted composition
 -- * '.:' function composition
@@ -132,8 +145,9 @@
   , Intro.Trustworthy.IsList(
       Item
       , fromList
-      -- , toList -- provided by Foldable
+      -- , toList -- renamed to asList
       )
+  , asList
   , convertList
   , fromFoldable
   , Data.List.break
@@ -156,6 +170,7 @@
   , Data.List.isPrefixOf
   , Data.List.isSuffixOf
   , Data.List.iterate
+  , Data.List.iterate'
   , Data.List.lookup
   , Data.List.Extra.nubOrd
   , Data.List.Extra.nubOrdBy
@@ -179,6 +194,7 @@
   , Data.List.take
   , Data.List.Extra.takeEnd
   , Data.List.takeWhile
+  , Data.List.Extra.takeWhileEnd
   , Data.List.transpose
   , Data.List.unfoldr
   , Data.List.unzip
@@ -187,7 +203,6 @@
   , Data.List.zip3
   , Data.List.zipWith
   , Data.List.zipWith3
-  -- , Data.List.cycle -- partial
   , Safe.headDef
   , Safe.headMay -- prefer pattern match
   , Safe.initDef
@@ -204,6 +219,11 @@
   -- (<|), -- in lens
   , Data.List.NonEmpty.scanl1
   , Data.List.NonEmpty.scanr1
+  , Data.List.NonEmpty.head
+  , Data.List.NonEmpty.init
+  , Data.List.NonEmpty.last
+  , Data.List.NonEmpty.tail
+  , Data.List.NonEmpty.cycle
 
   -- ** Tuple
   , Data.Tuple.fst
@@ -242,10 +262,13 @@
   -- ** ByteString
   , Data.ByteString.ByteString
   , LByteString
+  , Data.ByteString.Short.ShortByteString
 
-  -- ** Conversion
+  -- ** String conversion
   , Data.String.IsString(fromString)
-  , Data.String.Conversions.ConvertibleStrings(convertString)
+  , Intro.ConvertString.ConvertString(convertString)
+  , Intro.ConvertString.EncodeString(encodeString, decodeString, decodeStringLenient)
+  , Lenient(..)
 
   -- * Container types
 
@@ -265,9 +288,6 @@
   -- ** Seq
   , Data.Sequence.Seq
 
-  -- ** DList
-  , Intro.Trustworthy.DList
-
   -- * Numeric types
 
   -- ** Big integers
@@ -290,18 +310,12 @@
   , Prelude.Float
   , Prelude.Double
 
-  -- ** Rational
-  , Data.Ratio.Ratio
-  , Data.Ratio.Rational
-  , (Data.Ratio.%)
-  , Data.Ratio.numerator
-  , Data.Ratio.denominator
-  , Data.Ratio.approxRational
-
   -- * Numeric type classes
 
   -- ** Num
-  , Prelude.Num((+), (-), (*), negate, abs, signum, fromInteger)
+  , Prelude.Num((+), (-), (*), negate, abs, signum
+      -- fromInteger
+      )
   , Prelude.subtract
   , (Prelude.^) -- partial functions!
 
@@ -311,7 +325,11 @@
 
   -- ** Integral
   , Prelude.Integral(quot, rem, div, mod, quotRem, divMod, toInteger) -- partial functions!
-  , Prelude.fromIntegral
+  , Intro.ConvertIntegral.ToIntegral(..)
+  , Data.Bits.toIntegralSized
+  , Intro.ConvertIntegral.fromIntegralUnsafe
+  , Intro.ConvertIntegral.fromIntegerUnsafe
+  --, Prelude.fromIntegral
   , Prelude.even
   , Prelude.odd
   --, Prelude.gcd
@@ -347,37 +365,29 @@
 
   -- ** Show
   , Text.Show.Show
-#if MIN_VERSION_base(4,9,0)
   , Data.Functor.Classes.Show1
   , Data.Functor.Classes.Show2
-#endif
   , show
   , showT
   , showS
 
   -- ** Read
   , Text.Read.Read
-#if MIN_VERSION_base(4,9,0)
   , Data.Functor.Classes.Read1
   , Data.Functor.Classes.Read2
-#endif
   , readMaybe
 
-  -- * Equality and Ordering
+  -- * Equality and ordering
 
   -- ** Eq
   , Data.Eq.Eq((==), (/=))
-#if MIN_VERSION_base(4,9,0)
   , Data.Functor.Classes.Eq1
   , Data.Functor.Classes.Eq2
-#endif
 
   -- ** Ord
   , Data.Ord.Ord(compare, (<), (>), (<=), (>=), max, min)
-#if MIN_VERSION_base(4,9,0)
   , Data.Functor.Classes.Ord1
   , Data.Functor.Classes.Ord2
-#endif
   , Data.Ord.Ordering(LT,GT,EQ)
   , Data.Ord.Down(Down)
   , Data.Ord.comparing
@@ -432,11 +442,21 @@
       )
   , (Data.Functor.$>)
   , (Data.Functor.<$>)
+  , (Data.Functor.<&>)
   , map
   , Data.Functor.void
   , Control.Applicative.Const(Const, getConst) -- Data.Functor.Const
   , Data.Functor.Identity.Identity(Identity, runIdentity)
 
+  -- ** Contravariant
+  , Data.Functor.Contravariant.Contravariant(
+      (>$),
+      contramap
+      )
+  , (Data.Functor.Contravariant.$<)
+  , (Data.Functor.Contravariant.>$<)
+  , (Data.Functor.Contravariant.>$$<)
+
   -- ** Foldable
   , Data.Foldable.Foldable(elem, fold, foldMap,
            foldr, foldr',
@@ -459,17 +479,17 @@
   , Data.Foldable.notElem
   , Data.Foldable.sequenceA_
   , Safe.Foldable.foldl1May
-  , Safe.Foldable.foldl1Def
   , Safe.Foldable.foldr1May
-  , Safe.Foldable.foldr1Def
   , Safe.Foldable.maximumByMay
-  , Safe.Foldable.maximumByDef
+  , Safe.Foldable.maximumBoundBy
   , Safe.Foldable.minimumByMay
-  , Safe.Foldable.minimumByDef
+  , Safe.Foldable.minimumBoundBy
   , Safe.Foldable.maximumMay
-  , Safe.Foldable.maximumDef
+  , Safe.Foldable.maximumBounded
+  , Safe.Foldable.maximumBound
   , Safe.Foldable.minimumMay
-  , Safe.Foldable.minimumDef
+  , Safe.Foldable.minimumBounded
+  , Safe.Foldable.minimumBound
 
   -- ** Traversable
   , Data.Traversable.Traversable(traverse, sequenceA)
@@ -493,15 +513,10 @@
 
   -- ** Monad
   , Control.Monad.Monad((>>=))
-#if MIN_VERSION_base(4,9,0)
-  , Control.Monad.Fail.MonadFail
-#endif
-  , fail
   , Control.Monad.Fix.MonadFix(mfix)
   , (Control.Monad.=<<)
   , (Control.Monad.<=<)
   , (Control.Monad.>=>)
-  , Control.Monad.MonadPlus(mzero, mplus)
   , Control.Monad.join
   , Control.Monad.guard
   , Control.Monad.when
@@ -538,10 +553,11 @@
   , Data.Bitraversable.bifor
   , Data.Bitraversable.bisequenceA
 
-  -- * Monad transformer
+  -- * Effects and monad transformers
   , Control.Monad.Trans.MonadTrans(lift)
 
-  -- ** MaybeT
+  -- ** MonadPlus and MaybeT
+  , Control.Monad.MonadPlus
   , Control.Monad.Trans.Maybe.MaybeT(MaybeT, runMaybeT)
   , Control.Monad.Trans.Maybe.mapMaybeT
 
@@ -615,26 +631,19 @@
   , GHC.Generics.Generic
   , GHC.Generics.Generic1
   , Data.Typeable.Typeable
-  , Control.DeepSeq.NFData
-  , Data.Binary.Binary
 
   -- * Type level
-#if MIN_VERSION_base(4,9,0)
   , Data.Kind.Type
-#endif
   , Intro.Trustworthy.Constraint
   , Data.Proxy.Proxy(Proxy)
-  , Data.Tagged.Tagged(Tagged)
-  , Data.Tagged.unTagged
+  --, Data.Tagged.Tagged(Tagged)
+  --, Data.Tagged.unTagged
 
   -- * IO
   , System.IO.IO
   , Control.Monad.Trans.MonadIO(liftIO)
 
   -- ** Console
-  , getChar
-  , getContents
-  , getLine
   , print
   , putChar
   , putStr
@@ -650,13 +659,18 @@
   , writeFileUtf8
   , appendFileUtf8
 
-  -- * Error and Debugging
+  -- * Error handling and debugging
+  , HasCallStack
+  , Control.Monad.Fail.MonadFail
+  , fail
   , panic
   , undefined
   , Intro.Trustworthy.trace
   , Intro.Trustworthy.traceIO
+  , Intro.Trustworthy.traceId
   , Intro.Trustworthy.traceM
   , Intro.Trustworthy.traceShow
+  , Intro.Trustworthy.traceShowId
   , Intro.Trustworthy.traceShowM
 ) where
 
@@ -668,18 +682,18 @@
 import Data.Maybe (Maybe, fromMaybe)
 import Data.Semigroup (Semigroup((<>)))
 import Data.String (IsString(fromString), String)
-import Data.String.Conversions (ConvertibleStrings(convertString))
 import Data.Text (Text)
-import Intro.Trustworthy (HasCallStack, IsList(Item, toList, fromList))
+import Intro.ConvertIntegral
+import Intro.ConvertString
+import Intro.Trustworthy
 import System.IO (FilePath)
-import Text.Read (Read)
 import Text.Show (Show)
 import qualified Control.Applicative
 import qualified Control.Category
-import qualified Control.DeepSeq
 import qualified Control.Monad
 import qualified Control.Monad.Except
 import qualified Control.Monad.Extra
+import qualified Control.Monad.Fail
 import qualified Control.Monad.Fix
 import qualified Control.Monad.RWS.CPS
 import qualified Control.Monad.Reader
@@ -689,25 +703,28 @@
 import qualified Control.Monad.Writer.CPS
 import qualified Data.Bifoldable
 import qualified Data.Bifunctor
-import qualified Data.Binary
 import qualified Data.Bitraversable
 import qualified Data.Bits
 import qualified Data.Bool
 import qualified Data.ByteString
 import qualified Data.ByteString.Lazy
+import qualified Data.ByteString.Short
 import qualified Data.Either
 import qualified Data.Either.Extra
 import qualified Data.Eq
 import qualified Data.Foldable
 import qualified Data.Function
 import qualified Data.Functor
+import qualified Data.Functor.Classes
 import qualified Data.Functor.Identity
+import qualified Data.Functor.Contravariant
 import qualified Data.HashMap.Strict
 import qualified Data.HashSet
 import qualified Data.Hashable
 import qualified Data.Int
 import qualified Data.IntMap
 import qualified Data.IntSet
+import qualified Data.Kind
 import qualified Data.List
 import qualified Data.List.Extra
 import qualified Data.List.NonEmpty
@@ -716,11 +733,9 @@
 import qualified Data.Monoid
 import qualified Data.Ord
 import qualified Data.Proxy
-import qualified Data.Ratio
 import qualified Data.Semigroup
 import qualified Data.Sequence
 import qualified Data.Set
-import qualified Data.Tagged
 import qualified Data.Text.IO
 import qualified Data.Text.Lazy
 import qualified Data.Traversable
@@ -729,7 +744,6 @@
 import qualified Data.Void
 import qualified Data.Word
 import qualified GHC.Generics
-import qualified Intro.Trustworthy
 import qualified Numeric.Natural
 import qualified Prelude
 import qualified Safe
@@ -738,12 +752,6 @@
 import qualified Text.Read
 import qualified Text.Show
 
-#if MIN_VERSION_base(4,9,0)
-import qualified Control.Monad.Fail
-import qualified Data.Functor.Classes
-import qualified Data.Kind
-#endif
-
 -- | Alias for lazy 'Data.Text.Lazy.Text'
 type LText = Data.Text.Lazy.Text
 
@@ -762,6 +770,12 @@
 convertList = fromList . toList
 {-# INLINE convertList #-}
 
+-- | The 'asList' function extracts a list of @Item a@ from the structure @a@.
+--   It should satisfy fromList . asList = id.
+asList :: (IsList a) => a -> [Item a]
+asList = Intro.Trustworthy.toList
+{-# INLINE asList #-}
+
 -- | A synonym for 'fmap'.
 --
 --   @map = 'fmap'@
@@ -769,9 +783,9 @@
 map = fmap
 {-# INLINE map #-}
 
--- | Convert a value to a readable string type supported by 'ConvertibleStrings' using the 'Show' instance.
-show :: (Show a, IsString s) => a -> s
-show = fromString . showS
+-- | Convert a value to a readable string type supported by 'ConvertString' using the 'Show' instance.
+show :: (Show a, ConvertString String s) => a -> s
+show = convertString . showS
 {-# INLINE show #-}
 
 -- | Convert a value to a readable 'Text' using the 'Show' instance.
@@ -786,7 +800,7 @@
 
 -- | Parse a string type using the 'Text.Read.Read' instance.
 -- Succeeds if there is exactly one valid result.
-readMaybe :: (Text.Read.Read b, ConvertibleStrings a String) => a -> Maybe b
+readMaybe :: (Text.Read.Read b, ConvertString a String) => a -> Maybe b
 readMaybe = Text.Read.readMaybe . convertString
 {-# INLINE readMaybe #-}
 
@@ -806,27 +820,6 @@
 print = liftIO . System.IO.print
 {-# INLINE print #-}
 
--- | The 'getContents' operation returns all user input as a strict 'Text'.
---
--- __Note__: This function is lifted to the 'MonadIO' class.
-getContents :: MonadIO m => m Text
-getContents = liftIO Data.Text.IO.getContents
-{-# INLINE getContents #-}
-
--- | Read a line from the standard input device as a strict 'Text'.
---
--- __Note__: This function is lifted to the 'MonadIO' class.
-getLine :: MonadIO m => m Text
-getLine = liftIO Data.Text.IO.getLine
-{-# INLINE getLine #-}
-
--- | Read a character from the standard input device.
---
--- __Note__: This function is lifted to the 'MonadIO' class.
-getChar :: MonadIO m => m Char
-getChar = liftIO System.IO.getChar
-{-# INLINE getChar #-}
-
 -- | Write a strict 'Text' to the standard output device.
 --
 -- __Note__: This function is lifted to the 'MonadIO' class.
@@ -870,10 +863,12 @@
 {-# INLINE appendFile #-}
 
 -- | Read an entire file strictly into a 'Text' using UTF-8 encoding.
+-- The decoding is done using 'decodeStringLenient'. Invalid characters are replaced
+-- by the Unicode replacement character '\FFFD'.
 --
 -- __Note__: This function is lifted to the 'MonadIO' class.
 readFileUtf8 :: MonadIO m => FilePath -> m Text
-readFileUtf8 = map convertString . readFile
+readFileUtf8 = map decodeStringLenient . readFile
 {-# INLINE readFileUtf8 #-}
 
 -- | Write a 'Text' to a file using UTF-8 encoding.
@@ -893,7 +888,7 @@
 -- | Throw an undefined error. Use only for debugging.
 undefined :: HasCallStack => a
 undefined = Prelude.undefined
-{-# WARNING undefined "'undefined' remains in code" #-}
+{-# WARNING undefined "'undefined' should be used only for debugging" #-}
 
 -- | '<>' lifted to 'Control.Applicative.Applicative'
 (<>^) :: (Control.Applicative.Applicative f, Semigroup a) => f a -> f a -> f a
@@ -956,11 +951,6 @@
 -- @
 -- fail _ = mzero
 -- @
-#if MIN_VERSION_base(4,9,0)
 fail :: Control.Monad.Fail.MonadFail m => Text -> m a
 fail = Control.Monad.Fail.fail . convertString
-#else
-fail :: Control.Monad.Monad m => Text -> m a
-fail = Control.Monad.fail . convertString
-#endif
 {-# INLINE fail #-}
diff --git a/src/Intro/ConvertIntegral.hs b/src/Intro/ConvertIntegral.hs
new file mode 100644
--- /dev/null
+++ b/src/Intro/ConvertIntegral.hs
@@ -0,0 +1,79 @@
+-----------------------------------------------------------------------------
+-- |
+-- Module      :  Intro.ConvertInt
+-- Copyright   :  (c) Daniel Mendler 2020
+-- License     :  MIT
+--
+-- Maintainer  :  mail@daniel-mendler.de
+-- Stability   :  experimental
+-- Portability :  portable
+--
+-- Integer conversion
+--
+-----------------------------------------------------------------------------
+
+{-# LANGUAGE Safe #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE DefaultSignatures #-}
+
+module Intro.ConvertIntegral (
+  ToIntegral(..)
+  , fromIntegralUnsafe
+  , fromIntegerUnsafe
+) where
+
+import Data.Int
+import Data.Word
+
+class ToIntegral a b where
+  toIntegral :: a -> b
+  default toIntegral :: (Integral a, Integral b) => a -> b
+  toIntegral = fromIntegral
+  {-# INLINE toIntegral #-}
+
+instance ToIntegral Word8 Int16
+instance ToIntegral Word8 Int32
+instance ToIntegral Word8 Int64
+instance ToIntegral Word8 Int
+instance ToIntegral Word16 Int32
+instance ToIntegral Word16 Int64
+instance ToIntegral Word16 Int
+instance ToIntegral Word32 Int64
+
+instance ToIntegral Word8 Word16
+instance ToIntegral Word8 Word32
+instance ToIntegral Word8 Word64
+instance ToIntegral Word8 Word
+instance ToIntegral Word16 Word32
+instance ToIntegral Word16 Word64
+instance ToIntegral Word16 Word
+instance ToIntegral Word32 Word64
+
+instance ToIntegral Int8 Integer
+instance ToIntegral Int16 Integer
+instance ToIntegral Int32 Integer
+instance ToIntegral Int64 Integer
+instance ToIntegral Int Integer
+
+instance ToIntegral Word8 Integer
+instance ToIntegral Word16 Integer
+instance ToIntegral Word32 Integer
+instance ToIntegral Word64 Integer
+instance ToIntegral Word Integer
+
+instance ToIntegral Int8 Int16
+instance ToIntegral Int8 Int32
+instance ToIntegral Int8 Int64
+instance ToIntegral Int8 Int
+instance ToIntegral Int16 Int32
+instance ToIntegral Int16 Int64
+instance ToIntegral Int16 Int
+instance ToIntegral Int32 Int64
+
+fromIntegralUnsafe :: (Integral a, Num b) => a -> b
+fromIntegralUnsafe = fromIntegral
+{-# INLINE fromIntegralUnsafe #-}
+
+fromIntegerUnsafe :: Num a => Integer -> a
+fromIntegerUnsafe = fromInteger
+{-# INLINE fromIntegerUnsafe #-}
diff --git a/src/Intro/ConvertString.hs b/src/Intro/ConvertString.hs
new file mode 100644
--- /dev/null
+++ b/src/Intro/ConvertString.hs
@@ -0,0 +1,171 @@
+-----------------------------------------------------------------------------
+-- |
+-- Module      :  Intro.ConvertString
+-- Copyright   :  (c) Daniel Mendler 2017
+-- License     :  MIT
+--
+-- Maintainer  :  mail@daniel-mendler.de
+-- Stability   :  experimental
+-- Portability :  portable
+--
+-- String conversion and decoding
+--
+-----------------------------------------------------------------------------
+
+{-# LANGUAGE DeriveFoldable #-}
+{-# LANGUAGE DeriveFunctor #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DeriveTraversable #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+{-# LANGUAGE Safe #-}
+
+module Intro.ConvertString (
+    ConvertString(..)
+  , EncodeString(..)
+  , Lenient(..)
+) where
+
+import Data.ByteString (ByteString)
+import Data.ByteString.Short (ShortByteString)
+import Data.Either.Extra (eitherToMaybe)
+import Data.Eq (Eq)
+import Data.Foldable (Foldable)
+import Data.Function (id, (.))
+import Data.Functor (Functor(fmap))
+import Data.Maybe (Maybe)
+import Data.Ord (Ord)
+import Data.String (String)
+import Data.Text (Text)
+import Data.Text.Encoding.Error (lenientDecode)
+import Data.Traversable (Traversable)
+import Data.Word (Word8)
+import GHC.Generics (Generic, Generic1)
+import Text.Read (Read)
+import Text.Show (Show)
+import qualified Data.ByteString as B
+import qualified Data.ByteString.Lazy as BL
+import qualified Data.ByteString.Short as S
+import qualified Data.Text as T
+import qualified Data.Text.Encoding as TE
+import qualified Data.Text.Lazy as TL
+import qualified Data.Text.Lazy.Encoding as TLE
+
+-- | Conversion of strings to other string types
+--
+-- @
+-- ('convertString' :: b -> a)         . ('convertString' :: a -> b) ≡ ('id'      :: a -> a)
+-- ('convertString' :: b -> 'Maybe' a)   . ('convertString' :: a -> b) ≡ ('Just'    :: a -> 'Maybe' a)
+-- ('convertString' :: b -> 'Lenient' a) . ('convertString' :: a -> b) ≡ ('Lenient' :: a -> 'Lenient' a)
+-- @
+class ConvertString a b where
+  -- | Convert a string to another string type
+  convertString :: a -> b
+
+-- | Encode and decode strings as a byte sequence
+--
+-- @
+-- 'decodeString'        . 'encodeString' ≡ 'Just'
+-- 'decodeStringLenient' . 'encodeString' ≡ 'id'
+-- @
+class (ConvertString a b, ConvertString b (Maybe a), ConvertString b (Lenient a)) => EncodeString a b where
+  -- | Encode a string as a byte sequence
+  encodeString :: a -> b
+  encodeString = convertString
+  {-# INLINE encodeString #-}
+
+  -- | Lenient decoding of byte sequence
+  --
+  -- Lenient means that invalid characters are replaced
+  -- by the Unicode replacement character '\FFFD'.
+  decodeStringLenient :: b -> a
+  decodeStringLenient = getLenient . convertString
+  {-# INLINE decodeStringLenient #-}
+
+  -- | Decode byte sequence
+  --
+  -- If the decoding fails, return Nothing.
+  decodeString :: b -> Maybe a
+  decodeString = convertString
+  {-# INLINE decodeString #-}
+
+-- | Newtype wrapper for a string which was decoded leniently.
+newtype Lenient a = Lenient { getLenient :: a }
+  deriving (Eq, Ord, Read, Show, Functor, Foldable, Traversable, Generic, Generic1)
+
+instance ConvertString BL.ByteString   (Lenient String)  where {-# INLINE convertString #-}; convertString = Lenient . TL.unpack . TLE.decodeUtf8With lenientDecode
+instance ConvertString BL.ByteString   (Lenient TL.Text) where {-# INLINE convertString #-}; convertString = Lenient . TLE.decodeUtf8With lenientDecode
+instance ConvertString BL.ByteString   (Lenient Text)    where {-# INLINE convertString #-}; convertString = Lenient . TE.decodeUtf8With lenientDecode . BL.toStrict
+instance ConvertString BL.ByteString   (Maybe   String)  where {-# INLINE convertString #-}; convertString = fmap TL.unpack . eitherToMaybe . TLE.decodeUtf8'
+instance ConvertString BL.ByteString   (Maybe   TL.Text) where {-# INLINE convertString #-}; convertString = eitherToMaybe . TLE.decodeUtf8'
+instance ConvertString BL.ByteString   (Maybe   Text)    where {-# INLINE convertString #-}; convertString = eitherToMaybe . TE.decodeUtf8' . BL.toStrict
+instance ConvertString BL.ByteString   BL.ByteString     where {-# INLINE convertString #-}; convertString = id
+instance ConvertString BL.ByteString   ByteString        where {-# INLINE convertString #-}; convertString = BL.toStrict
+instance ConvertString BL.ByteString   ShortByteString   where {-# INLINE convertString #-}; convertString = S.toShort . BL.toStrict
+instance ConvertString BL.ByteString   [Word8]           where {-# INLINE convertString #-}; convertString = BL.unpack
+instance ConvertString ByteString      (Lenient String)  where {-# INLINE convertString #-}; convertString = Lenient . T.unpack . TE.decodeUtf8With lenientDecode
+instance ConvertString ByteString      (Lenient TL.Text) where {-# INLINE convertString #-}; convertString = Lenient . TLE.decodeUtf8With lenientDecode . BL.fromStrict
+instance ConvertString ByteString      (Lenient Text)    where {-# INLINE convertString #-}; convertString = Lenient . TE.decodeUtf8With lenientDecode
+instance ConvertString ByteString      (Maybe   String)  where {-# INLINE convertString #-}; convertString = fmap T.unpack . eitherToMaybe . TE.decodeUtf8'
+instance ConvertString ByteString      (Maybe   TL.Text) where {-# INLINE convertString #-}; convertString = eitherToMaybe . TLE.decodeUtf8' . BL.fromStrict
+instance ConvertString ByteString      (Maybe   Text)    where {-# INLINE convertString #-}; convertString = eitherToMaybe . TE.decodeUtf8'
+instance ConvertString ByteString      BL.ByteString     where {-# INLINE convertString #-}; convertString = BL.fromStrict
+instance ConvertString ByteString      ByteString        where {-# INLINE convertString #-}; convertString = id
+instance ConvertString ByteString      ShortByteString   where {-# INLINE convertString #-}; convertString = S.toShort
+instance ConvertString ByteString      [Word8]           where {-# INLINE convertString #-}; convertString = B.unpack
+instance ConvertString ShortByteString (Lenient String)  where {-# INLINE convertString #-}; convertString = Lenient . T.unpack . TE.decodeUtf8With lenientDecode . S.fromShort
+instance ConvertString ShortByteString (Lenient TL.Text) where {-# INLINE convertString #-}; convertString = Lenient . TLE.decodeUtf8With lenientDecode . BL.fromStrict . S.fromShort
+instance ConvertString ShortByteString (Lenient Text)    where {-# INLINE convertString #-}; convertString = Lenient . TE.decodeUtf8With lenientDecode . S.fromShort
+instance ConvertString ShortByteString (Maybe   String)  where {-# INLINE convertString #-}; convertString = fmap T.unpack . eitherToMaybe . TE.decodeUtf8' . S.fromShort
+instance ConvertString ShortByteString (Maybe   TL.Text) where {-# INLINE convertString #-}; convertString = eitherToMaybe . TLE.decodeUtf8' . BL.fromStrict . S.fromShort
+instance ConvertString ShortByteString (Maybe   Text)    where {-# INLINE convertString #-}; convertString = eitherToMaybe . TE.decodeUtf8' . S.fromShort
+instance ConvertString ShortByteString BL.ByteString     where {-# INLINE convertString #-}; convertString = BL.fromStrict . S.fromShort
+instance ConvertString ShortByteString ByteString        where {-# INLINE convertString #-}; convertString = S.fromShort
+instance ConvertString ShortByteString ShortByteString   where {-# INLINE convertString #-}; convertString = id
+instance ConvertString ShortByteString [Word8]           where {-# INLINE convertString #-}; convertString = S.unpack
+instance ConvertString String          BL.ByteString     where {-# INLINE convertString #-}; convertString = TLE.encodeUtf8 . TL.pack
+instance ConvertString String          ByteString        where {-# INLINE convertString #-}; convertString = TE.encodeUtf8 . T.pack
+instance ConvertString String          ShortByteString   where {-# INLINE convertString #-}; convertString = S.toShort . TE.encodeUtf8 . T.pack
+instance ConvertString String          String            where {-# INLINE convertString #-}; convertString = id
+instance ConvertString String          TL.Text           where {-# INLINE convertString #-}; convertString = TL.pack
+instance ConvertString String          Text              where {-# INLINE convertString #-}; convertString = T.pack
+instance ConvertString String          [Word8]           where {-# INLINE convertString #-}; convertString = BL.unpack . TLE.encodeUtf8 . TL.pack
+instance ConvertString TL.Text         BL.ByteString     where {-# INLINE convertString #-}; convertString = TLE.encodeUtf8
+instance ConvertString TL.Text         ByteString        where {-# INLINE convertString #-}; convertString = BL.toStrict . TLE.encodeUtf8
+instance ConvertString TL.Text         ShortByteString   where {-# INLINE convertString #-}; convertString = S.toShort . BL.toStrict . TLE.encodeUtf8
+instance ConvertString TL.Text         String            where {-# INLINE convertString #-}; convertString = TL.unpack
+instance ConvertString TL.Text         TL.Text           where {-# INLINE convertString #-}; convertString = id
+instance ConvertString TL.Text         Text              where {-# INLINE convertString #-}; convertString = TL.toStrict
+instance ConvertString TL.Text         [Word8]           where {-# INLINE convertString #-}; convertString = BL.unpack . TLE.encodeUtf8
+instance ConvertString Text            BL.ByteString     where {-# INLINE convertString #-}; convertString = BL.fromStrict . TE.encodeUtf8
+instance ConvertString Text            ByteString        where {-# INLINE convertString #-}; convertString = TE.encodeUtf8
+instance ConvertString Text            ShortByteString   where {-# INLINE convertString #-}; convertString = S.toShort . TE.encodeUtf8
+instance ConvertString Text            String            where {-# INLINE convertString #-}; convertString = T.unpack
+instance ConvertString Text            TL.Text           where {-# INLINE convertString #-}; convertString = TL.fromStrict
+instance ConvertString Text            Text              where {-# INLINE convertString #-}; convertString = id
+instance ConvertString Text            [Word8]           where {-# INLINE convertString #-}; convertString = BL.unpack . BL.fromStrict . TE.encodeUtf8
+instance ConvertString [Word8]         (Lenient String)  where {-# INLINE convertString #-}; convertString = Lenient . TL.unpack . TLE.decodeUtf8With lenientDecode . BL.pack
+instance ConvertString [Word8]         (Lenient TL.Text) where {-# INLINE convertString #-}; convertString = Lenient . TLE.decodeUtf8With lenientDecode . BL.pack
+instance ConvertString [Word8]         (Lenient Text)    where {-# INLINE convertString #-}; convertString = Lenient . TE.decodeUtf8With lenientDecode . B.pack
+instance ConvertString [Word8]         (Maybe String)    where {-# INLINE convertString #-}; convertString = fmap TL.unpack . eitherToMaybe . TLE.decodeUtf8' . BL.pack
+instance ConvertString [Word8]         (Maybe TL.Text)   where {-# INLINE convertString #-}; convertString = eitherToMaybe . TLE.decodeUtf8' . BL.pack
+instance ConvertString [Word8]         (Maybe Text)      where {-# INLINE convertString #-}; convertString = eitherToMaybe . TE.decodeUtf8' . B.pack
+instance ConvertString [Word8]         BL.ByteString     where {-# INLINE convertString #-}; convertString = BL.pack
+instance ConvertString [Word8]         ByteString        where {-# INLINE convertString #-}; convertString = B.pack
+instance ConvertString [Word8]         ShortByteString   where {-# INLINE convertString #-}; convertString = S.pack
+instance ConvertString [Word8]         [Word8]           where {-# INLINE convertString #-}; convertString = id
+
+instance EncodeString  String          BL.ByteString
+instance EncodeString  String          ByteString
+instance EncodeString  String          ShortByteString
+instance EncodeString  String          [Word8]
+instance EncodeString  TL.Text         BL.ByteString
+instance EncodeString  TL.Text         ByteString
+instance EncodeString  TL.Text         ShortByteString
+instance EncodeString  TL.Text         [Word8]
+instance EncodeString  Text            BL.ByteString
+instance EncodeString  Text            ByteString
+instance EncodeString  Text            ShortByteString
+instance EncodeString  Text            [Word8]
diff --git a/src/Intro/Trustworthy.hs b/src/Intro/Trustworthy.hs
--- a/src/Intro/Trustworthy.hs
+++ b/src/Intro/Trustworthy.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE CPP #-}
 {-# LANGUAGE ConstraintKinds #-}
 {-# LANGUAGE KindSignatures #-}
 {-# LANGUAGE NoImplicitPrelude #-}
@@ -7,7 +6,7 @@
 -----------------------------------------------------------------------------
 -- |
 -- Module      :  Intro.Trustworthy
--- Copyright   :  (c) Daniel Mendler 2016
+-- Copyright   :  (c) Daniel Mendler 2016-2017
 -- License     :  MIT
 --
 -- Maintainer  :  mail@daniel-mendler.de
@@ -19,8 +18,7 @@
 -----------------------------------------------------------------------------
 
 module Intro.Trustworthy (
-  Data.DList.DList
-  , GHC.Exts.IsList(
+  GHC.Exts.IsList(
       Item
       , fromList
       , toList
@@ -29,8 +27,10 @@
   , HasCallStack
   , trace
   , traceIO
+  , traceId
   , traceM
   , traceShow
+  , traceShowId
   , traceShowM
   , Data.Hashable.Lifted.Hashable1
   , Data.Hashable.Lifted.Hashable2
@@ -40,22 +40,12 @@
 import Data.Function ((.))
 import Data.Text (Text, unpack)
 import Text.Show (Show)
-import qualified Data.DList
 import qualified Debug.Trace
 import qualified GHC.Exts
 import qualified Data.Hashable.Lifted
-
-#if MIN_VERSION_base(4,9,0)
 import Control.Applicative (Applicative)
 import Data.Kind (Constraint)
 import GHC.Stack (HasCallStack)
-#define APPLICATIVE Applicative
-#else
-import Control.Monad (Monad)
-import GHC.Exts (Constraint)
-type HasCallStack = (() :: GHC.Exts.Constraint)
-#define APPLICATIVE Monad
-#endif
 
 -- | The 'trace' function outputs the trace message given as its first argument,
 -- before returning the second argument as its result.
@@ -70,7 +60,7 @@
 -- trace message.
 trace :: Text -> a -> a
 trace = Debug.Trace.trace . unpack
-{-# WARNING trace "'trace' remains in code" #-}
+{-# WARNING trace "'trace' should be used only for debugging" #-}
 
 -- | Like 'trace' but returning unit in an arbitrary 'Applicative' context. Allows
 -- for convenient use in do-notation.
@@ -87,9 +77,9 @@
 -- >   traceM $ "x: " ++ show x
 -- >   y <- ...
 -- >   traceM $ "y: " ++ show y
-traceM :: APPLICATIVE m => Text -> m ()
+traceM :: Applicative m => Text -> m ()
 traceM = Debug.Trace.traceM . unpack
-{-# WARNING traceM "'traceM' remains in code" #-}
+{-# WARNING traceM "'traceM' should be used only for debugging" #-}
 
 -- | Like 'trace', but uses 'show' on the argument to convert it to a 'String'.
 --
@@ -104,7 +94,7 @@
 -- >     ...
 traceShow :: Show a => a -> b -> b
 traceShow = Debug.Trace.traceShow
-{-# WARNING traceShow "'traceShow' remains in code" #-}
+{-# WARNING traceShow "'traceShow' should be used only for debugging" #-}
 
 -- | Like 'traceM', but uses 'show' on the argument to convert it to a 'String'.
 --
@@ -113,12 +103,22 @@
 -- >   traceShowM $ x
 -- >   y <- ...
 -- >   traceShowM $ x + y
-traceShowM :: (Show a, APPLICATIVE m) => a -> m ()
+traceShowM :: (Show a, Applicative m) => a -> m ()
 traceShowM = Debug.Trace.traceShowM
-{-# WARNING traceShowM "'traceShowM' remains in code" #-}
+{-# WARNING traceShowM "'traceShowM' should be used only for debugging" #-}
 
 -- | The 'traceIO' function outputs the trace message from the IO monad.
 -- This sequences the output with respect to other IO actions.
 traceIO :: MonadIO m => Text -> m ()
 traceIO = liftIO . Debug.Trace.traceIO . unpack
-{-# WARNING traceIO "'traceIO' remains in code" #-}
+{-# WARNING traceIO "'traceIO' should be used only for debugging" #-}
+
+-- | Like 'traceShow' but returns the shown value instead of a third value.
+traceShowId :: Show a => a -> a
+traceShowId = Debug.Trace.traceShowId
+{-# WARNING traceShowId "'traceShowId' should be used only for debugging" #-}
+
+-- | Like 'trace' but returns the message instead of a third value.
+traceId :: Text -> Text
+traceId a = Debug.Trace.trace (unpack a) a
+{-# WARNING traceId "'traceId' should be used only for debugging" #-}
diff --git a/test/BaseCompat.hs b/test/BaseCompat.hs
--- a/test/BaseCompat.hs
+++ b/test/BaseCompat.hs
@@ -1,5 +1,4 @@
 {-# LANGUAGE NoImplicitPrelude #-}
-{-# LANGUAGE CPP #-}
 module BaseCompat (
   module X
 ) where
@@ -38,7 +37,7 @@
 import Data.IORef as X
 import Data.Int as X
 import Data.Ix as X
-import Data.List as X hiding (scanl1, scanr1, map)
+import Data.List as X hiding (scanl1, scanr1, map, cycle, head, init, last, tail)
 import Data.Maybe as X
 import Data.Monoid as X hiding (First(..), Last(..), (<>))
 import Data.Ord as X
@@ -58,7 +57,7 @@
 import Data.Word as X
 import Numeric as X
 import Numeric.Natural as X
-import Prelude as X hiding ((.), id, map, putChar, putStrLn, putStr, getContents, getLine, print, getChar, appendFile, readFile, writeFile, fail, show, undefined, scanl1, scanr1)
+import Prelude as X hiding ((.), id, map, putChar, putStrLn, putStr, getContents, getLine, print, getChar, appendFile, readFile, writeFile, fail, show, undefined, scanl1, scanr1, cycle, head, init, last, tail)
 import System.CPUTime as X
 import System.Console.GetOpt as X hiding (Option)
 import System.Environment as X
@@ -77,8 +76,5 @@
 import Text.Read as X hiding (readMaybe, get, lift, EOF, (+++))
 import Text.Show as X hiding (show)
 import Unsafe.Coerce as X
-
-#if MIN_VERSION_base(4,9,0)
 import Control.Monad.Fail as X hiding (fail)
 import Data.Kind as X
-#endif
diff --git a/test/OpticsCompat.hs b/test/OpticsCompat.hs
new file mode 100644
--- /dev/null
+++ b/test/OpticsCompat.hs
@@ -0,0 +1,7 @@
+{-# LANGUAGE NoImplicitPrelude #-}
+module OpticsCompat (
+  module X
+) where
+
+import Intro as X
+import Optics as X
diff --git a/test/compat.hs b/test/compat.hs
deleted file mode 100644
--- a/test/compat.hs
+++ /dev/null
@@ -1,9 +0,0 @@
-{-# LANGUAGE NoImplicitPrelude #-}
-{-# LANGUAGE OverloadedStrings #-}
-module Main where
-
-import BaseCompat
-import LensCompat
-
-main :: IO ()
-main = pure ()
diff --git a/test/test.hs b/test/test.hs
new file mode 100644
--- /dev/null
+++ b/test/test.hs
@@ -0,0 +1,74 @@
+{-# LANGUAGE ExplicitForAll #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+module Main where
+
+import BaseCompat ()
+import Data.ByteString.Short (ShortByteString)
+import Intro
+import LensCompat ()
+import OpticsCompat ()
+import Test.QuickCheck
+import qualified Data.ByteString as B
+import qualified Data.ByteString.Lazy as LB
+import qualified Data.ByteString.Short as SB
+import qualified Data.Text as T
+import qualified Data.Text.Lazy as LT
+
+instance Arbitrary ByteString      where arbitrary = B.pack <$> arbitrary
+instance Arbitrary LB.ByteString   where arbitrary = LB.pack <$> arbitrary
+instance Arbitrary ShortByteString where arbitrary = SB.pack <$> arbitrary
+instance Arbitrary Text            where arbitrary = T.pack <$> arbitrary
+instance Arbitrary LT.Text         where arbitrary = LT.pack <$> arbitrary
+
+main :: IO ()
+main = do
+  encode (Proxy :: Proxy LText)           (Proxy :: Proxy ByteString)
+  encode (Proxy :: Proxy LText)           (Proxy :: Proxy LByteString)
+  encode (Proxy :: Proxy LText)           (Proxy :: Proxy ShortByteString)
+  encode (Proxy :: Proxy LText)           (Proxy :: Proxy [Word8])
+  encode (Proxy :: Proxy String)          (Proxy :: Proxy ByteString)
+  encode (Proxy :: Proxy String)          (Proxy :: Proxy LByteString)
+  encode (Proxy :: Proxy String)          (Proxy :: Proxy ShortByteString)
+  encode (Proxy :: Proxy String)          (Proxy :: Proxy [Word8])
+  encode (Proxy :: Proxy Text)            (Proxy :: Proxy ByteString)
+  encode (Proxy :: Proxy Text)            (Proxy :: Proxy LByteString)
+  encode (Proxy :: Proxy Text)            (Proxy :: Proxy ShortByteString)
+  encode (Proxy :: Proxy Text)            (Proxy :: Proxy [Word8])
+  iso    (Proxy :: Proxy ByteString)      (Proxy :: Proxy ByteString)
+  iso    (Proxy :: Proxy ByteString)      (Proxy :: Proxy LByteString)
+  iso    (Proxy :: Proxy ByteString)      (Proxy :: Proxy ShortByteString)
+  iso    (Proxy :: Proxy ByteString)      (Proxy :: Proxy [Word8])
+  iso    (Proxy :: Proxy ShortByteString) (Proxy :: Proxy ByteString)
+  iso    (Proxy :: Proxy ShortByteString) (Proxy :: Proxy LByteString)
+  iso    (Proxy :: Proxy ShortByteString) (Proxy :: Proxy ShortByteString)
+  iso    (Proxy :: Proxy ShortByteString) (Proxy :: Proxy [Word8])
+  iso    (Proxy :: Proxy LByteString)     (Proxy :: Proxy ByteString)
+  iso    (Proxy :: Proxy LByteString)     (Proxy :: Proxy LByteString)
+  iso    (Proxy :: Proxy LByteString)     (Proxy :: Proxy ShortByteString)
+  iso    (Proxy :: Proxy LByteString)     (Proxy :: Proxy [Word8])
+  iso    (Proxy :: Proxy LText)           (Proxy :: Proxy LText)
+  iso    (Proxy :: Proxy LText)           (Proxy :: Proxy String)
+  iso    (Proxy :: Proxy LText)           (Proxy :: Proxy Text)
+  iso    (Proxy :: Proxy String)          (Proxy :: Proxy LText)
+  iso    (Proxy :: Proxy String)          (Proxy :: Proxy String)
+  iso    (Proxy :: Proxy String)          (Proxy :: Proxy Text)
+  iso    (Proxy :: Proxy Text)            (Proxy :: Proxy LText)
+  iso    (Proxy :: Proxy Text)            (Proxy :: Proxy String)
+  iso    (Proxy :: Proxy Text)            (Proxy :: Proxy Text)
+  iso    (Proxy :: Proxy [Word8])         (Proxy :: Proxy ByteString)
+  iso    (Proxy :: Proxy [Word8])         (Proxy :: Proxy LByteString)
+  iso    (Proxy :: Proxy [Word8])         (Proxy :: Proxy ShortByteString)
+  iso    (Proxy :: Proxy [Word8])         (Proxy :: Proxy [Word8])
+
+iso :: forall a b proxy. (Eq a, Eq b, Show a, Show b, Arbitrary a, Arbitrary b, ConvertString a b, ConvertString b a) => proxy a -> proxy b -> IO ()
+iso _ _ = do
+  quickCheck $ \(a :: a) -> convertString (convertString a :: b) == a
+  quickCheck $ \(b :: b) -> convertString (convertString b :: a) == b
+
+encode :: forall a b proxy. (Eq a, Show a, Arbitrary a, EncodeString a b) => proxy a -> proxy b -> IO ()
+encode _ _ = do
+  quickCheck $ \(a :: a) -> decodeString (encodeString a :: b) == Just a
+  quickCheck $ \(a :: a) -> decodeStringLenient (encodeString a :: b) == a
