diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
 # Revision history for byteslice
 
+## 0.2.7.0 -- 2022-02-16
+
+* Add support for GHC 9.2.
+* Drop support for GHC 8.8 and earlier.
+* Add `foldlM` and `foldrM` for mondic folds over byte sequences.
+
 ## 0.2.6.0 -- 2021-09-15
 
 * Add `BytesN` and `ByteArrayN`.
diff --git a/byteslice.cabal b/byteslice.cabal
--- a/byteslice.cabal
+++ b/byteslice.cabal
@@ -1,6 +1,6 @@
 cabal-version: 2.4
 name: byteslice
-version: 0.2.6.0
+version: 0.2.7.0
 synopsis: Slicing managed and unmanaged memory
 description:
   This library provides types that allow the user to talk about a slice of
@@ -37,11 +37,11 @@
     Data.Bytes.Byte
     Data.Bytes.Pure
     Data.Bytes.IO
-    UnliftedBytes
+    Reps
     Cstrlen
   build-depends:
-    , base >=4.11.1 && <5
-    , bytestring >=0.10.8 && <0.11
+    , base >=4.14 && <5
+    , bytestring >=0.10.8 && <0.12
     , primitive >=0.7 && <0.8
     , primitive-unlifted >=0.1.2 && <0.2
     , primitive-addr >=0.1 && <0.2
@@ -50,11 +50,10 @@
     , vector >=0.12 && <0.13
   hs-source-dirs: src
   ghc-options: -Wall -O2
-  if impl(ghc>=8.10)
-    hs-source-dirs: src-unlifted-newtypes
+  if impl(ghc>=9.2)
+    hs-source-dirs: src-new-reps
   else
-    hs-source-dirs: src-no-unlifted-newtypes
-    ghc-options: -fno-warn-dodgy-imports -fno-warn-dodgy-exports
+    hs-source-dirs: src-old-reps
   if impl(ghc>=9.0)
     hs-source-dirs: src-ghc-cstrlen
   else
@@ -82,6 +81,7 @@
     , tasty
     , tasty-hunit
     , tasty-quickcheck
+    , transformers
 
 benchmark bench
   type: exitcode-stdio-1.0
diff --git a/src-new-reps/Reps.hs b/src-new-reps/Reps.hs
new file mode 100644
--- /dev/null
+++ b/src-new-reps/Reps.hs
@@ -0,0 +1,16 @@
+{-# language GADTSyntax #-}
+{-# language KindSignatures #-}
+{-# language MagicHash #-}
+{-# language TypeInType #-}
+{-# language UnboxedTuples #-}
+{-# language UnliftedNewtypes #-}
+
+module Reps
+  ( Bytes#(..)
+  , word8ToWord#
+  ) where
+
+import GHC.Exts (ByteArray#,Int#,RuntimeRep(..),Levity(Unlifted),TYPE,word8ToWord#)
+
+newtype Bytes# :: TYPE ('TupleRep '[ 'BoxedRep 'Unlifted,'IntRep,'IntRep]) where
+  Bytes# :: (# ByteArray#, Int#, Int# #) -> Bytes#
diff --git a/src-no-unlifted-newtypes/UnliftedBytes.hs b/src-no-unlifted-newtypes/UnliftedBytes.hs
deleted file mode 100644
--- a/src-no-unlifted-newtypes/UnliftedBytes.hs
+++ /dev/null
@@ -1,23 +0,0 @@
-{-# language MagicHash #-}
-{-# language TypeFamilies #-}
-{-# language TypeInType #-}
-{-# language UndecidableInstances #-}
-
-module UnliftedBytes
-  ( Bytes#
-  , lift
-  , unlift
-  ) where
-
-import Data.Bytes.Internal (Bytes)
-import GHC.TypeLits
-import GHC.Exts (RuntimeRep(..),TYPE)
-
-type family Bytes# :: TYPE ('TupleRep '[ 'UnliftedRep,'IntRep,'IntRep]) where
-  Bytes# = TypeError ('Text "Bytes# not available before GHC 8.10")
-
-lift :: Bytes# -> Bytes
-lift _ = errorWithoutStackTrace "UnliftedBytes: lift not implemented"
-
-unlift :: Bytes -> Bytes#
-unlift _ = errorWithoutStackTrace "UnliftedBytes: unlift not implemented"
diff --git a/src-old-reps/Reps.hs b/src-old-reps/Reps.hs
new file mode 100644
--- /dev/null
+++ b/src-old-reps/Reps.hs
@@ -0,0 +1,22 @@
+{-# language GADTSyntax #-}
+{-# language KindSignatures #-}
+{-# language MagicHash #-}
+{-# language TypeInType #-}
+{-# language UnboxedTuples #-}
+{-# language UnliftedNewtypes #-}
+
+module Reps
+  ( Bytes#(..)
+  , word8ToWord#
+  ) where
+
+import GHC.Exts (ByteArray#,Int#,Word#,RuntimeRep(..),TYPE)
+
+newtype Bytes# :: TYPE ('TupleRep '[ 'UnliftedRep,'IntRep,'IntRep]) where
+  Bytes# :: (# ByteArray#, Int#, Int# #) -> Bytes#
+
+-- In GHC 9.2, the lifted Word8 type started being backed by the
+-- unlifted Word8# instead of by Word#. This is a compatibility hack.
+word8ToWord# :: Word# -> Word#
+{-# inline word8ToWord# #-}
+word8ToWord# w = w
diff --git a/src-unlifted-newtypes/UnliftedBytes.hs b/src-unlifted-newtypes/UnliftedBytes.hs
deleted file mode 100644
--- a/src-unlifted-newtypes/UnliftedBytes.hs
+++ /dev/null
@@ -1,26 +0,0 @@
-{-# language GADTSyntax #-}
-{-# language KindSignatures #-}
-{-# language MagicHash #-}
-{-# language TypeInType #-}
-{-# language UnboxedTuples #-}
-{-# language UnliftedNewtypes #-}
-
-module UnliftedBytes
-  ( Bytes#(..)
-  , lift
-  , unlift
-  ) where
-
-import Data.Bytes.Internal (Bytes(Bytes))
-import Data.Primitive (ByteArray(ByteArray))
-import GHC.Exts (Int(I#),ByteArray#,Int#,RuntimeRep(..),TYPE)
-
-newtype Bytes# :: TYPE ('TupleRep '[ 'UnliftedRep,'IntRep,'IntRep]) where
-  Bytes# :: (# ByteArray#, Int#, Int# #) -> Bytes#
-
-lift :: Bytes# -> Bytes
-lift (Bytes# (# arr, off, len #)) = Bytes (ByteArray arr) (I# off) (I# len)
-
-unlift :: Bytes -> Bytes#
-unlift (Bytes (ByteArray arr) (I# off) (I# len)) =
-  Bytes# (# arr, off, len #)
diff --git a/src/Data/Bytes.hs b/src/Data/Bytes.hs
--- a/src/Data/Bytes.hs
+++ b/src/Data/Bytes.hs
@@ -54,6 +54,9 @@
   , Pure.foldr'
     -- * Folds with Indices
   , Pure.ifoldl'
+    -- * Monadic Folds
+  , Pure.foldlM
+  , Pure.foldrM
     -- * Common Folds
   , elem
     -- * Splitting
@@ -162,7 +165,7 @@
 import GHC.Exts (Addr#,Word#,Int#)
 import GHC.Exts (Int(I#),Ptr(Ptr))
 import GHC.Word (Word8(W8#),Word32)
-import UnliftedBytes (lift,unlift)
+import Reps (Bytes#(..),word8ToWord#)
 
 import qualified Data.Bytes.Byte as Byte
 import qualified Data.Bytes.Chunks as Chunks
@@ -397,14 +400,14 @@
 
 -- | Is the byte a member of the byte sequence?
 elem :: Word8 -> Bytes -> Bool
-elem (W8# w) b = case elemLoop 0# w b of
+elem (W8# w) b = case elemLoop 0# (word8ToWord# w) b of
   1# -> True
   _ -> False
 
 elemLoop :: Int# -> Word# -> Bytes -> Int#
 elemLoop !r !w (Bytes arr@(ByteArray arr# ) off@(I# off# ) len) = case len of
   0 -> r
-  _ -> elemLoop (Exts.orI# r (Exts.eqWord# w (Exts.indexWord8Array# arr# off# ) )) w (Bytes arr (off + 1) (len - 1))
+  _ -> elemLoop (Exts.orI# r (Exts.eqWord# w (word8ToWord# (Exts.indexWord8Array# arr# off# )) )) w (Bytes arr (off + 1) (len - 1))
 
 -- | Take bytes while the predicate is true.
 takeWhile :: (Word8 -> Bool) -> Bytes -> Bytes
@@ -482,7 +485,7 @@
 -- to a byte sequence. Any character with a codepoint above @U+007F@ is
 -- replaced by @U+0000@.
 fromAsciiString :: String -> Bytes
-{-# DEPRECATED fromAsciiString "use Data.Bytes.Ascii.fromString instead" #-}
+{-# DEPRECATED fromAsciiString "use Data.Bytes.Text.Ascii.fromString instead" #-}
 {-# INLINE fromAsciiString #-}
 fromAsciiString = Ascii.fromString
 
@@ -490,13 +493,13 @@
 -- by ISO-8859-1. These are encoded with ISO-8859-1. Any character
 -- with a codepoint above @U+00FF@ is replaced by an unspecified byte.
 fromLatinString :: String -> Bytes
-{-# DEPRECATED fromLatinString "use Data.Bytes.Latin1.fromString instead" #-}
+{-# DEPRECATED fromLatinString "use Data.Bytes.Text.Latin1.fromString instead" #-}
 {-# INLINE fromLatinString #-}
 fromLatinString = Latin1.fromString
 
 -- | Interpret a byte sequence as text encoded by ISO-8859-1.
 toLatinString :: Bytes -> String
-{-# DEPRECATED toLatinString "use Data.Bytes.Latin1.toString instead" #-}
+{-# DEPRECATED toLatinString "use Data.Bytes.Text.Latin1.toString instead" #-}
 {-# INLINE toLatinString #-}
 toLatinString = Latin1.toString
 
@@ -718,3 +721,12 @@
 {-# DEPRECATED toLowerAsciiByteArrayClone "use Data.Bytes/Text/AsciiExt.toLowerU" #-}
 {-# INLINE toLowerAsciiByteArrayClone #-}
 toLowerAsciiByteArrayClone = AsciiExt.toLowerU
+
+lift :: Bytes# -> Bytes
+{-# inline lift #-}
+lift (Bytes# (# arr, off, len #)) = Bytes (ByteArray arr) (I# off) (I# len)
+
+unlift :: Bytes -> Bytes#
+{-# inline unlift #-}
+unlift (Bytes (ByteArray arr) (I# off) (I# len)) =
+  Bytes# (# arr, off, len #)
diff --git a/src/Data/Bytes/Pure.hs b/src/Data/Bytes/Pure.hs
--- a/src/Data/Bytes/Pure.hs
+++ b/src/Data/Bytes/Pure.hs
@@ -18,6 +18,8 @@
   , toPinnedByteArrayClone
   , fromByteArray
   , length
+  , foldlM
+  , foldrM
   , foldl
   , foldl'
   , foldr
@@ -151,6 +153,16 @@
     0 -> a
     _ -> go (f a (PM.indexByteArray arr off)) (off + 1) (len - 1)
 
+-- | Left monadic fold over bytes, non-strict in the accumulator.
+foldlM :: Monad m => (a -> Word8 -> m a) -> a -> Bytes -> m a
+{-# inline foldlM #-}
+foldlM f a0 (Bytes arr off0 len0) = go a0 off0 len0 where
+  go a !off !len = case len of
+    0 -> pure a
+    _ -> do
+      a' <- f a (PM.indexByteArray arr off)
+      go a' (off + 1) (len - 1)
+
 -- | Right fold over bytes, non-strict in the accumulator.
 foldr :: (Word8 -> a -> a) -> a -> Bytes -> a
 {-# inline foldr #-}
@@ -178,6 +190,17 @@
     (-1) -> a
     _ -> go (f (PM.indexByteArray arr off) a) (off - 1) (ix - 1)
 
+-- | Right monadic fold over bytes, non-strict in the accumulator.
+foldrM :: Monad m => (Word8 -> a -> m a) -> a -> Bytes -> m a
+{-# inline foldrM #-}
+foldrM f a0 (Bytes arr off0 len0) =
+  go a0 (off0 + len0 - 1) (len0 - 1) 
+  where
+  go !a !off !ix = case ix of
+    (-1) -> pure a
+    _ -> do
+      a' <- f (PM.indexByteArray arr off) a
+      go a' (off - 1) (ix - 1)
 
 -- | Yields a pointer to the beginning of the byte sequence. It is only safe
 -- to call this on a 'Bytes' backed by a pinned @ByteArray@.
diff --git a/src/Data/Bytes/Text/AsciiExt.hs b/src/Data/Bytes/Text/AsciiExt.hs
--- a/src/Data/Bytes/Text/AsciiExt.hs
+++ b/src/Data/Bytes/Text/AsciiExt.hs
@@ -45,7 +45,7 @@
 -- To maintain a running state, see 'hFoldLines'.
 --
 -- Lines are extracted with with 'BC8.hGetLine', which does not document its
--- dectection algorithm. As of writing (bytestring v0.11.1.0), lines are
+-- detection algorithm. As of writing (bytestring v0.11.1.0), lines are
 -- delimited by a single @\n@ character (UNIX-style, as all things should be).
 hForLines_ :: Handle -> (Bytes -> IO a) -> IO ()
 hForLines_ h body = loop
@@ -61,7 +61,7 @@
 -- If you do not need to keep a state, see `hForLines_`.
 --
 -- Lines are extracted with with 'BC8.hGetLine', which does not document its
--- dectection algorithm. As of writing (bytestring v0.11.1.0), lines are
+-- detection algorithm. As of writing (bytestring v0.11.1.0), lines are
 -- delimited by a single @\n@ character (UNIX-style, as all things should be).
 hFoldLines :: Handle -> a -> (a -> Bytes -> IO a) -> IO a
 hFoldLines h z body = loop z
diff --git a/src/Data/Bytes/Text/Latin1.hs b/src/Data/Bytes/Text/Latin1.hs
--- a/src/Data/Bytes/Text/Latin1.hs
+++ b/src/Data/Bytes/Text/Latin1.hs
@@ -35,10 +35,10 @@
   ) where
 
 import Data.Bytes.Types (Bytes(..))
-import Data.Char (ord)
+import Data.Char (ord,chr)
 import Data.Primitive (ByteArray(ByteArray))
-import GHC.Exts (Int(I#),Char(C#),word2Int#,chr#)
-import GHC.Word (Word8(W8#))
+import Data.Word (Word8)
+import GHC.Exts (Int(I#),Char(C#))
 
 import qualified Data.Bytes.Pure as Bytes
 import qualified GHC.Exts as Exts
@@ -54,7 +54,7 @@
 -- | Interpret a byte sequence as text encoded by ISO-8859-1.
 toString :: Bytes -> String
 {-# INLINE toString #-}
-toString = Bytes.foldr (\(W8# w) xs -> C# (chr# (word2Int# w)) : xs) []
+toString = Bytes.foldr (\w xs -> chr (fromIntegral @Word8 @Int w) : xs) []
 
 -- TODO presumably also fromText and fromShortText
 
diff --git a/src/Data/Bytes/Types.hs b/src/Data/Bytes/Types.hs
--- a/src/Data/Bytes/Types.hs
+++ b/src/Data/Bytes/Types.hs
@@ -17,7 +17,7 @@
 import Data.Primitive (ByteArray(..),MutableByteArray(..))
 import Data.Primitive.Addr (Addr)
 import GHC.TypeNats (Nat)
-import UnliftedBytes (Bytes#(..))
+import Reps (Bytes#(..))
 
 -- | A slice of a 'ByteArray' whose compile-time-known length is represented
 -- by a phantom type variable. Consumers of this data constructor must be
diff --git a/test/Main.hs b/test/Main.hs
--- a/test/Main.hs
+++ b/test/Main.hs
@@ -17,6 +17,7 @@
 import Test.Tasty.HUnit ((@=?),testCase)
 import Test.Tasty.QuickCheck ((===),testProperty,property,Discard(Discard))
 import Test.Tasty.QuickCheck ((==>),Arbitrary)
+import Control.Monad.Trans.Writer (Writer,tell)
 
 import qualified Data.ByteString as ByteString
 import qualified Data.Bytes as Bytes
@@ -124,6 +125,16 @@
       List.foldl (-) 0 xs
       ===
       Bytes.foldl (-) 0 (Bytes.unsafeDrop 1 (Exts.fromList (x : xs)))
+  , testProperty "foldlM" $ \(x :: Word8) (xs :: [Word8]) ->
+      let f acc y = (tell [x] >> pure (acc - y)) :: Writer [Word8] Word8 in
+      Foldable.foldlM f 0 xs
+      ===
+      Bytes.foldlM f 0 (Bytes.unsafeDrop 1 (Exts.fromList (x : xs)))
+  , testProperty "foldrM" $ \(x :: Word8) (xs :: [Word8]) ->
+      let f acc y = (tell [x] >> pure (acc - y)) :: Writer [Word8] Word8 in
+      Foldable.foldrM f 0 xs
+      ===
+      Bytes.foldrM f 0 (Bytes.unsafeDrop 1 (Exts.fromList (x : xs)))
   , testProperty "foldl'" $ \(x :: Word8) (xs :: [Word8]) ->
       List.foldl' (-) 0 xs
       ===
