diff --git a/Debug/Hoed.hs b/Debug/Hoed.hs
--- a/Debug/Hoed.hs
+++ b/Debug/Hoed.hs
@@ -328,11 +328,13 @@
   , hoedCompTree    :: CompTree
   }
 
+-- | Configuration options for running Hoed
 data HoedOptions = HoedOptions
   { verbose     :: Verbosity
   , prettyWidth :: Int
   }
 
+-- | The default is to run silent and pretty print with a width of 110 characters
 defaultHoedOptions :: HoedOptions
 defaultHoedOptions = HoedOptions Silent 110
 
diff --git a/Debug/Hoed/Observe.lhs b/Debug/Hoed/Observe.lhs
--- a/Debug/Hoed/Observe.lhs
+++ b/Debug/Hoed/Observe.lhs
@@ -99,7 +99,8 @@
 import Control.Concurrent.MVar
 import Control.Monad
 import Data.Array as Array
-import qualified Data.HashTable.IO as H
+import Data.HashMap.Strict (HashMap)
+import qualified Data.HashMap.Strict as H
 import Data.IORef
 import Data.List (sortOn)
 import Data.Maybe
@@ -191,8 +192,8 @@
 endEventStream :: IO Trace
 endEventStream = do
   (stringsCount :!: stringsHashTable) <- takeMVar strings
-  unsortedStrings <- H.toList stringsHashTable
-  putMVar strings . (0 :!:) =<< H.new
+  let unsortedStrings = H.toList stringsHashTable
+  putMVar strings (0 :!: mempty)
   let stringsTable = V.unsafeAccum (\_ -> id) (V.replicate stringsCount (error "uninitialized")) [(i,s) | (s,i) <- unsortedStrings]
   writeIORef stringsLookupTable stringsTable
   reset (Proxy :: Proxy Vector) events
@@ -201,14 +202,20 @@
 sendEvent nodeId !parent !change = do
   write events nodeId (Event parent change)
 
+lookupOrAddString :: Text -> IO Int
 lookupOrAddString s = do
-  (stringsCount :!: stringsTable) <- takeMVar strings
-  value <- H.lookup stringsTable s
-  (count',res) <- case value of
-            Just x -> return (stringsCount, x)
-            Nothing -> H.insert stringsTable s stringsCount >> return (stringsCount+1, stringsCount)
-  putMVar strings (count' :!: stringsTable)
-  return res
+  (stringsCount :!: stringsTable) <- readMVar strings
+  case H.lookup s stringsTable of
+    Just x  -> return x
+    Nothing -> do
+      (stringsCount :!: stringsTable) <- takeMVar strings
+      let (count',table', res) =
+            case H.lookup s stringsTable of
+              Just x -> (stringsCount, stringsTable, x)
+              Nothing ->
+                (stringsCount+1, H.insert s stringsCount stringsTable, stringsCount)
+      putMVar strings (count' :!: table')
+      return res
 
 -- Global store of unboxed events.
 -- Since we cannot unbox Strings, these are represented as references to the
@@ -220,10 +227,9 @@
   return rope
 
 {-# NOINLINE strings #-}
-strings :: MVar(Pair Int (H.CuckooHashTable Text Int))
+strings :: MVar(Pair Int (HashMap Text Int))
 strings = unsafePerformIO $ do
-  h <- H.newSized 100000  -- suggested capacity for the hash table
-  newMVar (0 :!: h)
+  newMVar (0 :!: mempty)
 
 {-# NOINLINE stringsLookupTable #-}
 stringsLookupTable :: IORef (V.Vector Text)
diff --git a/Debug/Hoed/Render.hs b/Debug/Hoed/Render.hs
--- a/Debug/Hoed/Render.hs
+++ b/Debug/Hoed/Render.hs
@@ -34,7 +34,6 @@
 import           Data.Coerce
 import           Data.Hashable
 import           Data.List                (nub, sort, unfoldr)
-import qualified Data.HashTable.ST.Cuckoo as H
 import           Data.IntMap.Strict (IntMap)
 import qualified Data.IntMap.Strict as IntMap
 import           Data.Map.Strict (Map)
diff --git a/Hoed.cabal b/Hoed.cabal
--- a/Hoed.cabal
+++ b/Hoed.cabal
@@ -1,5 +1,5 @@
 name:                Hoed
-version:             0.5.0
+version:             0.5.1
 synopsis:            Lightweight algorithmic debugging.
 description:
     Hoed is a tracer and debugger for the programming language Haskell.
@@ -52,7 +52,6 @@
                        , bytestring
                        , cereal, cereal-text, cereal-vector
                        , hashable >= 1.2.5
-                       , hashtables
                        , QuickCheck
                        , open-browser
                        , primitive
@@ -63,6 +62,7 @@
                        , text
                        , transformers
                        , uniplate
+                       , unordered-containers
                        , vector
                        , vector-th-unbox
   default-language:    Haskell2010
diff --git a/Text/PrettyPrint/FPretty.hs b/Text/PrettyPrint/FPretty.hs
--- a/Text/PrettyPrint/FPretty.hs
+++ b/Text/PrettyPrint/FPretty.hs
@@ -118,6 +118,7 @@
 
 import Data.Hashable
 import Data.Maybe (fromJust)
+import Data.Semigroup
 import Data.Sequence as Dequeue (Seq, (<|), viewl, viewr, ViewL(..), ViewR(..))
 import qualified Data.Sequence as Dequeue (empty)
   -- Originally used Banker's dequeue from Okasaki's book 
@@ -127,7 +128,7 @@
 import Data.String
 import GHC.Generics
 
-infixr 6 <>,<+>
+infixr 6 <+>
 infixr 5 <$>,<$$>,</>,<//>
 
 ----------------------
@@ -218,10 +219,6 @@
 -- | Either nothing ('empty') or a new line.
 linebreak :: Doc
 
--- | Horizontal composition of two documents. 
--- Is associative with identity 'empty'.
-(<>) :: Doc -> Doc -> Doc
-
 -- | Mark document as group, that is, layout as a single line if possible.
 -- Within a group for all basic documents with several layouts the same layout
 -- is chosen, that is, they are all horizontal or all new lines.
@@ -257,11 +254,19 @@
 instance IsString Doc where
   fromString = text
 
+instance Semigroup Doc where
+  -- | Horizontal composition of two documents.
+  (<>) = (:<>)
+
+instance Monoid Doc where
+  mempty = empty
+#if !(MIN_VERSION_base(4,11,0))
+  mappend = (<>)
+#endif
 empty = Nil
 text t = Text (length t) t
 line = Line 1 " "
 linebreak = Line 0 ""
-(<>) = (:<>)
 group = Group
 nest = Nest
 align = Align 0
