diff --git a/flatparse.cabal b/flatparse.cabal
--- a/flatparse.cabal
+++ b/flatparse.cabal
@@ -5,7 +5,7 @@
 -- see: https://github.com/sol/hpack
 
 name:           flatparse
-version:        0.3.1.0
+version:        0.3.2.0
 synopsis:       High-performance parsing from strict bytestrings
 description:    @Flatparse@ is a high-performance parsing library, focusing on programming languages and
                 human-readable data formats. See the README for more information:
diff --git a/src/FlatParse/Basic.hs b/src/FlatParse/Basic.hs
--- a/src/FlatParse/Basic.hs
+++ b/src/FlatParse/Basic.hs
@@ -201,9 +201,7 @@
     OK# a s -> runParser# (f a) fp eob s
     x       -> unsafeCoerce# x
   {-# inline (>>=) #-}
-  Parser fa >> Parser fb = Parser \fp eob s -> case fa fp eob s of
-    OK# a s -> fb fp eob s
-    x       -> unsafeCoerce# x
+  (>>) = (*>)
   {-# inline (>>) #-}
 
 -- | Higher-level boxed data type for parsing results.
@@ -336,13 +334,13 @@
 char c = string [c]
 
 -- | Read a `Word8`.
-byte :: Word8 -> Parser e ()
-byte (W8# w) = ensureBytes# 1 >> scan8# (W# w)
+byte :: Word -> Parser e ()
+byte (W# w) = ensureBytes# 1 >> scan8# (W# w)
 {-# inline byte #-}
 
 -- | Read a sequence of bytes. This is a template function, you can use it as @$(bytes [3, 4, 5])@,
 --   for example, and the splice has type @Parser e ()@.
-bytes :: [Word8] -> Q Exp
+bytes :: [Word] -> Q Exp
 bytes bytes = do
   let !len = length bytes
   [| ensureBytes# len >> $(scanBytes# bytes) |]
@@ -523,11 +521,11 @@
 {-# inline anyWord8_ #-}
 
 -- | Parse any `Word16`.
-anyWord16 :: Parser e Word16
+anyWord16 :: Parser e Word
 anyWord16 = Parser \fp eob buf -> case 2# <=# minusAddr# eob buf of
   0# -> Fail#
-  _  -> case indexWord16OffAddr# buf 0# of
-    w -> OK# (W16# w) (plusAddr# buf 2#)
+  _  -> case indexWord16OffAddr buf 0# of
+    w -> OK# (W# w) (plusAddr# buf 2#)
 {-# inline anyWord16 #-}
 
 -- | Skip any `Word16`.
@@ -536,11 +534,11 @@
 {-# inline anyWord16_ #-}
 
 -- | Parse any `Word32`.
-anyWord32 :: Parser e Word32
+anyWord32 :: Parser e Word
 anyWord32 = Parser \fp eob buf -> case 4# <=# minusAddr# eob buf of
   0# -> Fail#
-  _  -> case indexWord32OffAddr# buf 0# of
-    w -> OK# (W32# w) (plusAddr# buf 4#)
+  _  -> case indexWord32OffAddr buf 0# of
+    w -> OK# (W# w) (plusAddr# buf 4#)
 {-# inline anyWord32 #-}
 
 -- | Skip any `Word32`.
@@ -906,7 +904,7 @@
 --   enough bytes.
 scan8# :: Word -> Parser e ()
 scan8# (W# c) = Parser \fp eob s ->
-  case indexWord8OffAddr# s 0# of
+  case indexWord8OffAddr s 0# of
     c' -> case eqWord# c c' of
       1# -> OK# () (plusAddr# s 1#)
       _  -> Fail#
@@ -916,7 +914,7 @@
 --   enough bytes.
 scan16# :: Word -> Parser e ()
 scan16# (W# c) = Parser \fp eob s ->
-  case indexWord16OffAddr# s 0# of
+  case indexWord16OffAddr s 0# of
     c' -> case eqWord# c c' of
       1# -> OK# () (plusAddr# s 2#)
       _  -> Fail#
@@ -926,7 +924,7 @@
 --   enough bytes.
 scan32# :: Word -> Parser e ()
 scan32# (W# c) = Parser \fp eob s ->
-  case indexWord32OffAddr# s 0# of
+  case indexWord32OffAddr s 0# of
     c' -> case eqWord# c c' of
       1# -> OK# () (plusAddr# s 4#)
       _  -> Fail#
@@ -936,15 +934,15 @@
 --   enough bytes.
 scan64# :: Word -> Parser e ()
 scan64# (W# c) = Parser \fp eob s ->
-  case indexWord64OffAddr# s 0# of
+  case indexWord64OffAddr s 0# of
     c' -> case eqWord# c c' of
       1# -> OK# () (plusAddr# s 8#)
       _  -> Fail#
 {-# inline scan64# #-}
 
 -- | Unsafely read and return a byte from the input. It's not checked that the input is non-empty.
-scanAny8# :: Parser e Word8
-scanAny8# = Parser \fp eob s -> OK# (W8# (indexWord8OffAddr# s 0#)) (plusAddr# s 1#)
+scanAny8# :: Parser e Word
+scanAny8# = Parser \fp eob s -> OK# (W# (indexWord8OffAddr s 0#)) (plusAddr# s 1#)
 {-# inline scanAny8# #-}
 
 scanPartial64# :: Int -> Word -> Parser e ()
@@ -966,7 +964,7 @@
 
 -- | Template function, creates a @Parser e ()@ which unsafely scans a given
 --   sequence of bytes.
-scanBytes# :: [Word8] -> Q Exp
+scanBytes# :: [Word] -> Q Exp
 scanBytes# bytes = do
   let !(leading, w8s) = splitBytes bytes
       !scanw8s        = go w8s where
diff --git a/src/FlatParse/Examples/BasicLambda/Lexer.hs b/src/FlatParse/Examples/BasicLambda/Lexer.hs
--- a/src/FlatParse/Examples/BasicLambda/Lexer.hs
+++ b/src/FlatParse/Examples/BasicLambda/Lexer.hs
@@ -65,7 +65,7 @@
       pos      = case e of Imprecise pos e -> pos
                            Precise pos e   -> pos
       ls       = FP.lines b
-      [(l, c)] = posLineCols b [pos]
+      (l, c)   = head $ posLineCols b [pos]
       line     = if l < length ls then ls !! l else ""
       linum    = show l
       lpad     = map (const ' ') linum
diff --git a/src/FlatParse/Internal.hs b/src/FlatParse/Internal.hs
--- a/src/FlatParse/Internal.hs
+++ b/src/FlatParse/Internal.hs
@@ -6,7 +6,6 @@
 import Data.Char
 import Data.Foldable (foldl')
 import Data.Map (Map)
-import Data.Word
 import GHC.Exts
 import GHC.ForeignPtr
 
@@ -27,12 +26,28 @@
 shortInteger :: Int# -> Integer
 #if MIN_VERSION_base(4,15,0)
 shortInteger = IS
-{-# inline shortInteger #-}
 #else
 shortInteger = S#
 #endif
+{-# inline shortInteger #-}
 
+#if MIN_VERSION_base(4,16,0)
+indexWord8OffAddr s x = word8ToWord# (indexWord8OffAddr# s x)
+indexWord16OffAddr s x = word16ToWord# (indexWord16OffAddr# s x)
+indexWord32OffAddr s x = word32ToWord# (indexWord32OffAddr# s x)
+indexWord64OffAddr s x = indexWord64OffAddr# s x
+#else
+indexWord8OffAddr  = indexWord8OffAddr#
+indexWord16OffAddr = indexWord16OffAddr#
+indexWord32OffAddr = indexWord32OffAddr#
+indexWord64OffAddr = indexWord64OffAddr#
+#endif
+{-# inline indexWord8OffAddr #-}
+{-# inline indexWord16OffAddr #-}
+{-# inline indexWord32OffAddr #-}
+{-# inline indexWord64OffAddr #-}
 
+
 -- Char predicates
 --------------------------------------------------------------------------------
 
@@ -61,7 +76,7 @@
 readInt' :: Int# -> Addr# -> Addr# -> (# Int#, Addr# #)
 readInt' acc s end = case eqAddr# s end of
   1# -> (# acc, s #)
-  _  -> case indexWord8OffAddr# s 0# of
+  _  -> case indexWord8OffAddr s 0# of
     w | 1# <- leWord# 48## w, 1# <- leWord# w 57## ->
       readInt' (mul10 acc +# (word2Int# w -# 48#)) (plusAddr# s 1#) end
     _ -> (# acc, s #)
@@ -128,9 +143,12 @@
 
 -- | Convert a `String` to an UTF-8-coded `B.ByteString`.
 packUTF8 :: String -> B.ByteString
-packUTF8 = B.pack . concatMap charToBytes
+packUTF8 str = B.pack $ do
+  c <- str
+  w <- charToBytes c
+  pure (fromIntegral w)
 
-charToBytes :: Char -> [Word8]
+charToBytes :: Char -> [Word]
 charToBytes c'
     | c <= 0x7f     = [fromIntegral c]
     | c <= 0x7ff    = [0xc0 .|. y, 0x80 .|. z]
@@ -144,16 +162,16 @@
     x = fromIntegral (unsafeShiftR c 12 .&. 0x3f)
     w = fromIntegral (unsafeShiftR c 18 .&. 0x7)
 
-strToBytes :: String -> [Word8]
+strToBytes :: String -> [Word]
 strToBytes = concatMap charToBytes
 {-# inline strToBytes #-}
 
-packBytes :: [Word8] -> Word
+packBytes :: [Word] -> Word
 packBytes = fst . foldl' go (0, 0) where
   go (acc, shift) w | shift == 64 = error "packWords: too many bytes"
   go (acc, shift) w = (unsafeShiftL (fromIntegral w) shift .|. acc, shift+8)
 
-splitBytes :: [Word8] -> ([Word8], [Word])
+splitBytes :: [Word] -> ([Word], [Word])
 splitBytes ws = case quotRem (length ws) 8 of
   (0, _) -> (ws, [])
   (_, r) -> (as, chunk8s bs) where
@@ -169,7 +187,7 @@
 -- Switch trie compilation
 --------------------------------------------------------------------------------
 
-data Trie a = Branch !a !(Map Word8 (Trie a))
+data Trie a = Branch !a !(Map Word (Trie a))
   deriving Show
 
 type Rule = Maybe Int
@@ -180,7 +198,7 @@
 updRule :: Int -> Maybe Int -> Maybe Int
 updRule rule = Just . maybe rule (min rule)
 
-insert :: Int -> [Word8] -> Trie Rule -> Trie Rule
+insert :: Int -> [Word] -> Trie Rule -> Trie Rule
 insert rule = go where
   go [] (Branch rule' ts) =
     Branch (updRule rule rule') ts
@@ -204,8 +222,8 @@
       ts'
 
 data Trie' a
-  = Branch' !a !(Map Word8 (Trie' a))
-  | Path !a ![Word8] !(Trie' a)
+  = Branch' !a !(Map Word (Trie' a))
+  | Path !a ![Word] !(Trie' a)
   deriving Show
 
 -- | Compress linear paths.
diff --git a/src/FlatParse/Stateful.hs b/src/FlatParse/Stateful.hs
--- a/src/FlatParse/Stateful.hs
+++ b/src/FlatParse/Stateful.hs
@@ -96,7 +96,7 @@
   -- ** Position and span conversions
   , Basic.validPos
   , Basic.posLineCols
-  , Basic.unsafeSpanToByteString
+  , unsafeSpanToByteString
   , Basic.unsafeSlice
   , Basic.mkPos
   , Basic.lines
@@ -126,9 +126,7 @@
 import Control.Monad
 import Data.Foldable
 import Data.Map (Map)
-import Data.Word
 import GHC.Exts
-import GHC.Word
 import Language.Haskell.TH
 import System.IO.Unsafe
 import GHC.ForeignPtr
@@ -208,9 +206,7 @@
     OK# a s n -> runParser# (f a) fp r eob s n
     x         -> unsafeCoerce# x
   {-# inline (>>=) #-}
-  Parser fa >> Parser fb = Parser \fp !r eob s n -> case fa fp r eob s n of
-    OK# a s n -> fb fp r eob s n
-    x         -> unsafeCoerce# x
+  (>>) = (*>)
   {-# inline (>>) #-}
 
 -- | Higher-level boxed data type for parsing results.
@@ -370,14 +366,14 @@
 char :: Char -> Q Exp
 char c = string [c]
 
--- | Read a `Word8`.
-byte :: Word8 -> Parser e ()
-byte (W8# w) = ensureBytes# 1 >> scan8# (W# w)
+-- | Read a byte.
+byte :: Word -> Parser e ()
+byte (W# w) = ensureBytes# 1 >> scan8# (W# w)
 {-# inline byte #-}
 
 -- | Read a sequence of bytes. This is a template function, you can use it as @$(bytes [3, 4, 5])@,
 --   for example, and the splice has type @Parser e ()@.
-bytes :: [Word8] -> Q Exp
+bytes :: [Word] -> Q Exp
 bytes bytes = do
   let !len = length bytes
   [| ensureBytes# len >> $(scanBytes# bytes) |]
@@ -540,25 +536,25 @@
 fusedSatisfy_ f1 f2 f3 f4 = () <$ fusedSatisfy f1 f2 f3 f4
 {-# inline fusedSatisfy_ #-}
 
--- | Parse any `Word8`.
-anyWord8 :: Parser e Word8
+-- | Parse any byte.
+anyWord8 :: Parser e Word
 anyWord8 = Parser \fp !r eob buf n -> case eqAddr# eob buf of
   1# -> Fail#
-  _  -> case indexWord8OffAddr# buf 0# of
-    w -> OK# (W8# w) (plusAddr# buf 1#) n
+  _  -> case indexWord8OffAddr buf 0# of
+    w -> OK# (W# w) (plusAddr# buf 1#) n
 {-# inline anyWord8 #-}
 
--- | Skip any `Word8`.
+-- | Skip any byte.
 anyWord8_ :: Parser e ()
 anyWord8_ = () <$ anyWord8
 {-# inline anyWord8_ #-}
 
 -- | Parse any `Word16`.
-anyWord16 :: Parser e Word16
+anyWord16 :: Parser e Word
 anyWord16 = Parser \fp !r eob buf n -> case 2# <=# minusAddr# eob buf of
   0# -> Fail#
-  _  -> case indexWord16OffAddr# buf 0# of
-    w -> OK# (W16# w) (plusAddr# buf 2#) n
+  _  -> case indexWord16OffAddr buf 0# of
+    w -> OK# (W# w) (plusAddr# buf 2#) n
 {-# inline anyWord16 #-}
 
 -- | Skip any `Word16`.
@@ -567,11 +563,11 @@
 {-# inline anyWord16_ #-}
 
 -- | Parse any `Word32`.
-anyWord32 :: Parser e Word32
+anyWord32 :: Parser e Word
 anyWord32 = Parser \fp !r eob buf n -> case 4# <=# minusAddr# eob buf of
   0# -> Fail#
-  _  -> case indexWord32OffAddr# buf 0# of
-    w -> OK# (W32# w) (plusAddr# buf 4#) n
+  _  -> case indexWord32OffAddr buf 0# of
+    w -> OK# (W# w) (plusAddr# buf 4#) n
 {-# inline anyWord32 #-}
 
 -- | Skip any `Word32`.
@@ -815,6 +811,14 @@
   x          -> unsafeCoerce# x
 {-# inline byteStringed #-}
 
+-- | Create a `B.ByteString` from a `Span`. The result is invalid is the `Span` points
+--   outside the current buffer, or if the `Span` start is greater than the end position.
+unsafeSpanToByteString :: Span -> Parser e B.ByteString
+unsafeSpanToByteString (Span l r) =
+  lookahead (setPos l >> byteStringOf (setPos r))
+{-# inline unsafeSpanToByteString #-}
+
+
 -- | Run a parser in a given input span. The input position and the `Int` state is restored after
 --   the parser is finished, so `inSpan` does not consume input and has no side effect.  Warning:
 --   this operation may crash if the given span points outside the current parsing buffer. It's
@@ -827,15 +831,7 @@
     x         -> unsafeCoerce# x
 {-# inline inSpan #-}
 
---------------------------------------------------------------------------------
 
--- | Create a `B.ByteString` from a `Span`. The result is invalid is the `Span` points
---   outside the current buffer, or if the `Span` start is greater than the end position.
-unsafeSpanToByteString :: Span -> Parser e B.ByteString
-unsafeSpanToByteString (Span l r) =
-  lookahead (setPos l >> byteStringOf (setPos r))
-{-# inline unsafeSpanToByteString #-}
-
 --------------------------------------------------------------------------------
 
 -- | Parse the rest of the current line as a `String`. Assumes UTF-8 encoding,
@@ -882,7 +878,7 @@
 --   enough bytes.
 scan8# :: Word -> Parser e ()
 scan8# (W# c) = Parser \fp !r eob s n ->
-  case indexWord8OffAddr# s 0# of
+  case indexWord8OffAddr s 0# of
     c' -> case eqWord# c c' of
       1# -> OK# () (plusAddr# s 1#) n
       _  -> Fail#
@@ -892,7 +888,7 @@
 --   enough bytes.
 scan16# :: Word -> Parser e ()
 scan16# (W# c) = Parser \fp !r eob s n ->
-  case indexWord16OffAddr# s 0# of
+  case indexWord16OffAddr s 0# of
     c' -> case eqWord# c c' of
       1# -> OK# () (plusAddr# s 2#) n
       _  -> Fail#
@@ -902,7 +898,7 @@
 --   enough bytes.
 scan32# :: Word -> Parser e ()
 scan32# (W# c) = Parser \fp !r eob s n ->
-  case indexWord32OffAddr# s 0# of
+  case indexWord32OffAddr s 0# of
     c' -> case eqWord# c c' of
       1# -> OK# () (plusAddr# s 4#) n
       _  -> Fail#
@@ -912,15 +908,15 @@
 --   enough bytes.
 scan64# :: Word -> Parser e ()
 scan64# (W# c) = Parser \fp !r eob s n ->
-  case indexWord64OffAddr# s 0# of
+  case indexWord64OffAddr s 0# of
     c' -> case eqWord# c c' of
       1# -> OK# () (plusAddr# s 8#) n
       _  -> Fail#
 {-# inline scan64# #-}
 
 -- | Unsafely read and return a byte from the input. It's not checked that the input is non-empty.
-scanAny8# :: Parser e Word8
-scanAny8# = Parser \fp !r eob s n -> OK# (W8# (indexWord8OffAddr# s 0#)) (plusAddr# s 1#) n
+scanAny8# :: Parser e Word
+scanAny8# = Parser \fp !r eob s n -> OK# (W# (indexWord8OffAddr s 0#)) (plusAddr# s 1#) n
 {-# inline scanAny8# #-}
 
 scanPartial64# :: Int -> Word -> Parser e ()
@@ -942,7 +938,7 @@
 
 -- | Template function, creates a @Parser e ()@ which unsafely scans a given
 --   sequence of bytes.
-scanBytes# :: [Word8] -> Q Exp
+scanBytes# :: [Word] -> Q Exp
 scanBytes# bytes = do
   let !(leading, w8s) = splitBytes bytes
       !scanw8s        = go w8s where
