diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,8 +1,12 @@
 # Changelog and Acknowledgements
 
+## 2.0.2
+* Fixed a minor issue where the caret indicating pattern location of a
+  `Pcre2CompileException` was misplaced if the pattern contained a newline.
+
 ## 2.0.1
 * Added `microlens` as a dependency to improve Haddock docs (`Traversal'` _et
-  al_ are clickable) and relieve maintenance burden somewhat.
+  al._ are clickable) and relieve maintenance burden somewhat.
 * Moderate refactoring of internals.
 
 ## 2.0.0
diff --git a/pcre2.cabal b/pcre2.cabal
--- a/pcre2.cabal
+++ b/pcre2.cabal
@@ -5,7 +5,7 @@
 -- see: https://github.com/sol/hpack
 
 name:           pcre2
-version:        2.0.1
+version:        2.0.2
 synopsis:       Regular expressions via the PCRE2 C library (included)
 description:    Please see the README on GitHub at <https://github.com/sjshuck/hs-pcre2>
 category:       Text
@@ -108,7 +108,7 @@
       Paths_pcre2
   hs-source-dirs:
       src/hs
-  ghc-options: -optc=-DPCRE2_CODE_UNIT_WIDTH=16 -optc=-DPCRE2_STATIC=1 -optc=-DHAVE_INTTYPES_H=1 -optc=-DHAVE_LIMITS_H=1 -optc=-DHAVE_MEMMOVE_H=1 -optc=-DHAVE_STDINT_H=1 -optc=-DHAVE_STDLIB_H=1 -optc=-DHAVE_STRERROR_H=1 -optc=-DHAVE_STRING_H=1 -optc=-DSUPPORT_JIT=1 -optc=-DSUPPORT_PCRE2_16=1 -optc=-DSUPPORT_UNICODE=1 -optc=-Wno-discarded-qualifiers -optc=-Wno-incompatible-pointer-types
+  ghc-options: -W -optc=-DPCRE2_CODE_UNIT_WIDTH=16 -optc=-DPCRE2_STATIC=1 -optc=-DHAVE_INTTYPES_H=1 -optc=-DHAVE_LIMITS_H=1 -optc=-DHAVE_MEMMOVE_H=1 -optc=-DHAVE_STDINT_H=1 -optc=-DHAVE_STDLIB_H=1 -optc=-DHAVE_STRERROR_H=1 -optc=-DHAVE_STRING_H=1 -optc=-DSUPPORT_JIT=1 -optc=-DSUPPORT_PCRE2_16=1 -optc=-DSUPPORT_UNICODE=1 -optc=-Wno-discarded-qualifiers -optc=-Wno-incompatible-pointer-types
   cc-options: -DHAVE_CONFIG_H -DPCRE2_CODE_UNIT_WIDTH=16
   include-dirs:
       src/c
diff --git a/src/hs/Text/Regex/Pcre2.hs b/src/hs/Text/Regex/Pcre2.hs
--- a/src/hs/Text/Regex/Pcre2.hs
+++ b/src/hs/Text/Regex/Pcre2.hs
@@ -6,8 +6,8 @@
     Atop the low-level binding to the C API, we present a high-level interface
     to add regular expressions to Haskell programs.
 
-    All input and output strings are strict `Text`, which maps directly to how
-    PCRE2 operates on strings of 16-bit-wide code units.
+    All input and output strings are strict `Data.Text.Text`, which maps
+    directly to how PCRE2 operates on strings of 16-bit-wide code units.
 
     The C API requires pattern strings to be compiled and the compiled patterns
     to be executed on subject strings in discrete steps.  We hide this
@@ -52,11 +52,11 @@
 
     [Subject]:  The string the compiled regular expression is executed on.
 
-    [Regex]:  A function of the form @`Text` -> result@, where the argument is
-    the subject.  It is \"compiled\" via partial application as discussed above.
-    (Lens users:  A regex has the more abstract form
+    [Regex]:  A function of the form @`Data.Text.Text` -> result@, where the
+    argument is the subject.  It is \"compiled\" via partial application as
+    discussed above.  (Lens users:  A regex has the more abstract form
     @[Traversal\'](https://hackage.haskell.org/package/microlens/docs/Lens-Micro.html#t:Traversal-39-)
-    `Text` result@, but the concept is the same.)
+    `Data.Text.Text` result@, but the concept is the same.)
 
     [Capture (or capture group)]:  Any substrings of the subject matched by the
     pattern, meaning the whole pattern and any parenthesized groupings.  The
@@ -68,8 +68,9 @@
     [Unset capture]:  A capture considered unset as distinct from empty.  This
     can arise from matching the pattern @(a)?@ to an empty subject&#x2014;the
     0th capture will be set as empty, but the 1st will be unset altogether.  We
-    represent both as empty `Text` for simplicity.  See below for discussions
-    about how unset captures may be detected or substituted using this library.
+    represent both as empty `Data.Text.Text` for simplicity.  See below for
+    discussions about how unset captures may be detected or substituted using
+    this library.
 
     [Named capture]:  A parenthesized capture can be named like this:
     @(?\<foo\>...)@.  Whether they have names or not, captures are always
@@ -100,9 +101,9 @@
     freely inlined; see below.
 
     Also of note is the optimization that, for each capture that\'s more than
-    half the length of the subject, a zero-copy `Text` is produced in constant
-    time and space.  This can yield a large performance boost in many cases,
-    for example when splitting lines into key-value pairs as in
+    half the length of the subject, a zero-copy `Data.Text.Text` is produced in
+    constant time and space.  This can yield a large performance boost in many
+    cases, for example when splitting lines into key-value pairs as in
     the [teaser](https://github.com/sjshuck/hs-pcre2#teasers).  A downside,
     however, is that retaining these slices in memory will carry the overhead
     of the dead portions of the subject (still guaranteed to be less than the
@@ -114,9 +115,9 @@
     [APIs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/matchAll)&#x2014;including
     previous versions of [this library](https://github.com/sjshuck/hs-pcre2/issues/17)&#x2014;where
     there are separate functions to request single versus global matching, we
-    accomplish this /(since 2.0.0)/ in a unified fashion using the `Alternative`
-    typeclass.  Typically the user will choose from two instances, `Maybe` and
-    `[]`:
+    accomplish this /(since 2.0.0)/ in a unified fashion using the
+    `Control.Applicative.Alternative` typeclass.  Typically the user will
+    choose from two instances, `Maybe` and `[]`:
 
     > b2 :: (Alternative f) => Text -> f Text
     > b2 = match "b.."
@@ -155,7 +156,7 @@
     >>> handle @SomePcre2Exception (\_ -> return Nothing) $ evaluate $ broken "foo"
     Nothing
 
-    Or simply select `IO` as the `Alternative` instance:
+    Or simply select `IO` as the `Control.Applicative.Alternative` instance:
 
     >>> handle @SomePcre2Exception (\_ -> return "broken") $ broken "foo"
     "broken"
@@ -190,8 +191,9 @@
     -- @[packed](https://hackage.haskell.org/package/microlens-platform/docs/Lens-Micro-Platform.html#v:packed)@
     -- and
     -- @[unpacked](https://hackage.haskell.org/package/microlens-platform/docs/Lens-Micro-Platform.html#v:packed)@
-    -- are included for working with `Text`, and it is upwards-compatible with
-    -- the full [lens](https://hackage.haskell.org/package/lens) library.
+    -- are included for working with `Data.Text.Text`, and it is
+    -- upwards-compatible with the
+    -- full [lens](https://hackage.haskell.org/package/lens) library.
     --
     -- We expose a set of traversals that focus on matched substrings within a
     -- subject.  Like the basic functional regexes, they should be \"compiled\"
@@ -347,7 +349,5 @@
     pcreVersion)
 where
 
-import Control.Applicative       (Alternative(..))
-import Data.Text                 (Text)
 import Text.Regex.Pcre2.Internal
 import Text.Regex.Pcre2.TH
diff --git a/src/hs/Text/Regex/Pcre2/Internal.hs b/src/hs/Text/Regex/Pcre2/Internal.hs
--- a/src/hs/Text/Regex/Pcre2/Internal.hs
+++ b/src/hs/Text/Regex/Pcre2/Internal.hs
@@ -2,6 +2,7 @@
 {-# LANGUAGE ExistentialQuantification #-}
 {-# LANGUAGE FunctionalDependencies #-}
 {-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE MultiWayIf #-}
 {-# LANGUAGE RankNTypes #-}
 {-# LANGUAGE RecordWildCards #-}
 {-# LANGUAGE TypeApplications #-}
@@ -12,14 +13,13 @@
 import           Control.Exception
 import           Control.Monad.State.Strict
 import           Data.Either                (partitionEithers)
-import           Data.Foldable              (toList)
+import           Data.Foldable              (foldl', toList)
 import           Data.Functor.Identity      (Identity(..))
 import           Data.IORef
 import           Data.IntMap.Strict         (IntMap, (!?))
 import qualified Data.IntMap.Strict         as IM
-import           Data.List                  (foldl', intercalate)
 import           Data.List.NonEmpty         (NonEmpty(..))
-import qualified Data.List.NonEmpty         as NE
+import           Data.Maybe                 (fromMaybe)
 import           Data.Monoid                (Alt(..), First)
 import           Data.Proxy                 (Proxy(..))
 import           Data.Text                  (Text)
@@ -58,6 +58,12 @@
 bitOr :: (Foldable t, Bits a) => t a -> a
 bitOr = foldl' (.|.) zeroBits
 
+-- | Like `lines`, but don\'t remove any characters.
+unchompedLines :: String -> [String]
+unchompedLines s = case break (== '\n') s of
+    (line,     _ : rest) -> (line ++ "\n") : unchompedLines rest
+    (lastLine, "")       -> [lastLine | not $ null lastLine]
+
 -- | Equivalent to @flip fix@.
 --
 -- Used to express a recursive function of one argument that is called only once
@@ -88,20 +94,29 @@
 -- | Zero-copy slice a 'Text'.  An unset capture is represented by a
 -- `pcre2_UNSET` range and is interpreted in this library as `Text.empty`.
 thinSlice :: Text -> Slice -> Text
-thinSlice text (Slice off offEnd)
-    | off == fromIntegral pcre2_UNSET = Text.empty
-    | otherwise                       = text
+thinSlice text slice = fromMaybe Text.empty $ maybeThinSlice text slice
+
+-- | Like `thinSlice`, but encode `pcre2_UNSET` as `Nothing`.
+maybeThinSlice :: Text -> Slice -> Maybe Text
+maybeThinSlice text (Slice off offEnd)
+    | off == fromIntegral pcre2_UNSET = Nothing
+    | otherwise                       = Just $ text
         & Text.takeWord16 offEnd
         & Text.dropWord16 off
 
--- | Slice a 'Text', copying if it\'s less than half of the original.
+-- | Slice a 'Text', copying if it\'s less than half of the original.  Note this
+-- is a lazy, pure operation.
 smartSlice :: Text -> Slice -> Text
-smartSlice text slice =
-    let substring = thinSlice text slice
-    in if Text.length substring > Text.length text `div` 2
-        then substring
-        else Text.copy substring
+smartSlice text slice = fromMaybe Text.empty $ maybeSmartSlice text slice
 
+-- | Like `smartSlice`, but only produce a result when not `pcre2_UNSET`.  It is
+-- forced when the outer `Maybe` constructor is forced.
+maybeSmartSlice :: Text -> Slice -> Maybe Text
+maybeSmartSlice text slice = f <$!> maybeThinSlice text slice where
+    f substring
+        | Text.length substring > Text.length text `div` 2 = substring
+        | otherwise                                        = Text.copy substring
+
 -- | Probably unnecessary, but unrestricted 'castPtr' feels dangerous.
 class CastCUs a b | a -> b where
     castCUs :: Ptr a -> Ptr b
@@ -730,8 +745,8 @@
 
 -- | Helper to generate public matching functions.
 pureUserMatcher :: Option -> Text -> Matcher
-pureUserMatcher option patt = matcherWithEnv matchEnv where
-    matchEnv = unsafePerformIO $ userMatchEnv option patt
+pureUserMatcher option patt =
+    matcherWithEnv $ unsafePerformIO $ userMatchEnv option patt
 
 -- | A `Subber` works by first writing results to a reasonably-sized buffer.  If
 -- we run out of room, PCRE2 allows us to simulate the rest of the substitution
@@ -939,11 +954,9 @@
         top <- pcre2_callout_block_capture_top blockPtr
         forM (0 :| [1 .. fromIntegral top - 1]) $ \n -> do
             [start, end] <- forM [0, 1] $ \i -> peekElemOff ovecPtr $ n * 2 + i
-            return $ if start == pcre2_UNSET
-                then Nothing
-                else Just $ smartSlice calloutSubject $ Slice
-                    (fromIntegral start)
-                    (fromIntegral end)
+            evaluate $ maybeSmartSlice calloutSubject $ Slice
+                (fromIntegral start)
+                (fromIntegral end)
 
     calloutMark <- do
         ptr <- pcre2_callout_block_mark blockPtr
@@ -978,11 +991,9 @@
         ovecCount <- pcre2_substitute_callout_block_oveccount blockPtr
         forM (0 :| [1 .. fromIntegral ovecCount - 1]) $ \n -> do
             [start, end] <- forM [0, 1] $ \i -> peekElemOff ovecPtr $ n * 2 + i
-            return $ if start == pcre2_UNSET
-                then Nothing
-                else Just $ smartSlice subCalloutSubject $ Slice
-                    (fromIntegral start)
-                    (fromIntegral end)
+            evaluate $ maybeSmartSlice subCalloutSubject $ Slice
+                (fromIntegral start)
+                (fromIntegral end)
 
     subCalloutReplacement <- do
         outPtr <- pcre2_substitute_callout_block_output blockPtr
@@ -1002,23 +1013,25 @@
 
 -- | The most general form of a matching function, which can also be used as a
 -- @Setter'@ to perform substitutions at the Haskell level.
-_capturesInternal :: (Traversable t) =>
+_gcaptures :: (Traversable t) =>
     Matcher -> FromMatch t -> Traversal' Text (t Text)
-_capturesInternal matcher fromMatch f subject =
-    traverse f captureTs <&> \captureTs' ->
-        -- Swag foldl-as-foldr to create only as many segments as we need to
-        -- stitch back together and no more.
-        let triples = concat $ zipWith3 zip3
-                (map toList sliceTs)
-                (map toList captureTs)
-                (map toList captureTs')
-        in Text.concat $ foldr mkSegments termSegments triples 0
+_gcaptures matcher fromMatch f subject = traverse f captureTs <&> \captureTs' ->
+    -- Swag foldl-as-foldr to create only as many segments as we need to stitch
+    -- back together and no more.
+    let beforeAndAfter = zip
+            (concatMap toList sliceAndCaptureTs)
+            (concatMap toList captureTs')
+    in Text.concat $ foldr mkSegments termSegments beforeAndAfter 0
 
     where
-    sliceTs = unsafeLazyStreamToList $ mapMS fromMatch $ matcher subject
-    captureTs = map (smartSlice subject <$>) sliceTs
+    sliceAndCaptureTs = unsafeLazyStreamToList $
+        mapMS (fromMatch >=> mapM enrichWithCapture) $ matcher subject
+    enrichWithCapture slice = do
+        capture <- evaluate $ smartSlice subject slice
+        return (slice, capture)
+    captureTs = map (snd <$>) sliceAndCaptureTs
 
-    mkSegments (Slice off offEnd, c, c') r prevOffEnd
+    mkSegments ((Slice off offEnd, c), c') r prevOffEnd
         | off == fromIntegral pcre2_UNSET || c == c' =
             -- This substring is unset or unchanged.  Keep going without making
             -- cuts.
@@ -1035,9 +1048,9 @@
         in [thinSlice subject (Slice off offEnd) | off /= offEnd]
 
 -- | A function that takes a C match result and extracts captures into a
--- container.  We need to pass this effectful callback to `_capturesInternal`
--- because of the latter\'s imperative loop that reuses the same
--- @pcre2_match_data@ block.
+-- container.  We need to pass this effectful callback to `_gcaptures` because
+-- of the latter\'s imperative loop that reuses the same @pcre2_match_data@
+-- block.
 --
 -- The container type is polymorphic and in practice carries a `Traversable`
 -- constraint.  Currently the following containers are used:
@@ -1101,8 +1114,8 @@
 
 -- | @matchesOpt mempty = matches@
 matchesOpt :: Option -> Text -> Text -> Bool
-matchesOpt option patt = has $ _capturesInternal matcher getNoSlices where
-    matcher = pureUserMatcher option patt
+matchesOpt option patt = has $
+    _gcaptures (pureUserMatcher option patt) getNoSlices
 
 -- | Match a pattern to a subject and return the portion(s) that matched in an
 -- `Alternative`, or `empty` if no match.
@@ -1112,6 +1125,8 @@
 match = matchOpt mempty
 
 -- | @matchOpt mempty = match@
+--
+-- @since 2.0.0
 matchOpt :: (Alternative f) => Option -> Text -> Text -> f Text
 matchOpt option patt = toAlternativeOf $ _matchOpt option patt
 
@@ -1140,8 +1155,8 @@
 -- subOpt SubGlobal = gsub
 -- @
 subOpt :: Option -> Text -> Text -> Text -> Text
-subOpt option patt replacement = snd . unsafePerformIO . subber where
-    subber = pureUserSubber option patt replacement
+subOpt option patt replacement =
+    snd . unsafePerformIO . pureUserSubber option patt replacement
 
 -- | Given a pattern, produce a traversal (0 or more targets) that focuses from
 -- a subject to each non-empty list of captures that pattern matches.
@@ -1184,8 +1199,7 @@
 
 -- | @_capturesOpt mempty = _captures@
 _capturesOpt :: Option -> Text -> Traversal' Text (NonEmpty Text)
-_capturesOpt option patt = _capturesInternal matcher getAllSlices where
-    matcher = pureUserMatcher option patt
+_capturesOpt option patt = _gcaptures (pureUserMatcher option patt) getAllSlices
 
 -- | Given a pattern, produce a traversal (0 or more targets) that focuses from
 -- a subject to the non-overlapping portions of it that match.
@@ -1196,8 +1210,8 @@
 
 -- | @_matchOpt mempty = _match@
 _matchOpt :: Option -> Text -> Traversal' Text Text
-_matchOpt option patt = _capturesInternal matcher get0thSlice . _Identity where
-    matcher = pureUserMatcher option patt
+_matchOpt option patt =
+    _gcaptures (pureUserMatcher option patt) get0thSlice . _Identity
 
 -- * Support for Template Haskell compile-time regex analysis
 
@@ -1262,13 +1276,26 @@
 -- last character).
 data Pcre2CompileException = Pcre2CompileException !CInt !Text !PCRE2_SIZE
 instance Show Pcre2CompileException where
-    show (Pcre2CompileException x patt offset) = intercalate "\n" $ [
-        "pcre2_compile: " ++ Text.unpack (getErrorMessage x),
-        replicate tab ' ' ++ Text.unpack patt] ++
-        [replicate (tab + offset') ' ' ++ "^" | offset' < Text.length patt]
+    show (Pcre2CompileException x patt offset) =
+        "pcre2_compile: " ++ Text.unpack (getErrorMessage x) ++ "\n" ++
+        concatMap (replicate tab ' ' ++) pattLinesMaybeWithCaret
         where
         tab = 20
+        pattLinesMaybeWithCaret
+            | offset' == Text.length patt = pattLines
+            | otherwise                   = insertCaretLine numberedPattLines
         offset' = fromIntegral offset
+        pattLines = unchompedLines $ Text.unpack patt
+        numberedPattLines = zip [0 ..] pattLines
+        (caretRow, caretCol) = (!! offset') $ do
+            (row, line) <- numberedPattLines
+            (col, _) <- zip [0 ..] line
+            return (row, col)
+        insertCaretLine = concatMap $ \(n, line) -> if
+            | n /= caretRow     -> [line]
+            | last line == '\n' -> [line, caretLine ++ "\n"]
+            | otherwise         -> [line ++ "\n", caretLine]
+        caretLine = replicate caretCol ' ' ++ "^"
 instance Exception Pcre2CompileException where
     toException = toException . SomePcre2Exception
     fromException = fromException >=> \(SomePcre2Exception e) -> cast e
diff --git a/src/hs/Text/Regex/Pcre2/TH.hs b/src/hs/Text/Regex/Pcre2/TH.hs
--- a/src/hs/Text/Regex/Pcre2/TH.hs
+++ b/src/hs/Text/Regex/Pcre2/TH.hs
@@ -15,7 +15,6 @@
 import           Control.Applicative        (Alternative(..))
 import           Data.IORef
 import           Data.List.NonEmpty         (NonEmpty(..))
-import qualified Data.List.NonEmpty         as NE
 import           Data.Map.Lazy              (Map)
 import qualified Data.Map.Lazy              as Map
 import           Data.Proxy                 (Proxy(..))
@@ -47,6 +46,9 @@
 -- be written with the help of @{-\# LANGUAGE PartialTypeSignatures \#-}@.
 newtype Captures (info :: CapturesInfo) = Captures (NonEmpty Text)
 
+captured :: Lens' (NonEmpty Text) (Captures info)
+captured f cs = f (Captures cs) <&> \(Captures cs') -> cs'
+
 -- | The kind of `Captures`\'s @info@.  The first number is the total number of
 -- parenthesized captures, and the list is a lookup table from capture names to
 -- numbers.
@@ -98,9 +100,8 @@
 -- | Like `capture` but focus from a `Captures` to a capture.
 _capture :: forall i info num. (CaptNum i info ~ num, KnownNat num) =>
     Lens' (Captures info) Text
-_capture f (Captures cs) =
-    let (ls, c : rs) = NE.splitAt (fromInteger $ natVal @num Proxy) cs
-    in f c <&> \c' -> Captures $ NE.fromList $ ls ++ c' : rs
+_capture = _Captures . singular (ix $ fromInteger $ natVal @num Proxy) where
+    _Captures f (Captures cs) = Captures <$> f cs
 
 -- | Unexported, top-level `IORef` that\'s created upon the first runtime
 -- evaluation of a Template Haskell `Matcher`.
@@ -134,13 +135,13 @@
     [] -> return Nothing
 
     -- One or more parenthesized captures.  Present
-    --     [Just "foo", Nothing, Nothing, Just "bar"]
+    --     [Just "foo", Nothing, Nothing, Just "bar", Nothing]
     -- as
-    --     '(4, '[ '("foo", 1), '("bar", 4)]).
+    --     '(5, '[ '("foo", 1), '("bar", 4)]).
     captureNames -> Just <$> promotedTupleT 2 `appT` hiQ `appT` kvsQ where
-        -- 4
+        -- 5
         hiQ = litT $ numTyLit $ fromIntegral $ length captureNames
-        -- '[ '("foo", 1), '("bar", 4), '("", 0)]
+        -- '[ '("foo", 1), '("bar", 4)]
         kvsQ = case toKVs captureNames of
             -- We can't splice '[] because it doesn't kind-check.  See above.
             []  -> [t| NoNamedCaptures |]
@@ -154,32 +155,29 @@
 -- | Helper for `regex` with no parenthesized captures.
 matchTH :: (Alternative f) => Text -> Text -> f Text
 matchTH patt = toAlternativeOf $
-    _capturesInternal (memoMatcher patt) get0thSlice . _Identity
+    _gcaptures (memoMatcher patt) get0thSlice . _Identity
 
 -- | Helper for `regex` with parenthesized captures.
 capturesTH :: (Alternative f) => Text -> Proxy info -> Text -> f (Captures info)
 capturesTH patt _ = toAlternativeOf $
-    _capturesInternal (memoMatcher patt) getAllSlices . to Captures
+    _gcaptures (memoMatcher patt) getAllSlices . captured
 
 -- | Helper for `regex` as a guard pattern.
 matchesTH :: Text -> Text -> Bool
-matchesTH patt = has $ _capturesInternal (memoMatcher patt) getNoSlices
+matchesTH patt = has $ _gcaptures (memoMatcher patt) getNoSlices
 
 -- | Helper for `regex` as a pattern that binds local variables.
-capturesNumberedTH :: Text -> NonEmpty Int -> Text -> [Text]
-capturesNumberedTH patt numbers = concatMap NE.toList . toListOf _cs where
-    _cs = _capturesInternal (memoMatcher patt) fromMatch
-    fromMatch = getWhitelistedSlices numbers
+capturesNumberedTH :: Text -> [Int] -> Text -> [Text]
+capturesNumberedTH patt numbers = view $
+    _gcaptures (memoMatcher patt) (getWhitelistedSlices numbers)
 
 -- | Helper for `_regex` with no parenthesized captures.
 _matchTH :: Text -> Traversal' Text Text
-_matchTH patt = _capturesInternal (memoMatcher patt) get0thSlice . _Identity
+_matchTH patt = _gcaptures (memoMatcher patt) get0thSlice . _Identity
 
 -- | Helper for `_regex` with parenthesized captures.
 _capturesTH :: Text -> Proxy info -> Traversal' Text (Captures info)
-_capturesTH patt _ = _cs . wrapped where
-    _cs = _capturesInternal (memoMatcher patt) getAllSlices
-    wrapped f cs = f (Captures cs) <&> \(Captures cs') -> cs'
+_capturesTH patt _ = _gcaptures (memoMatcher patt) getAllSlices . captured
 
 -- | === As an expression
 --
@@ -231,22 +229,22 @@
     quotePat = \s -> do
         captureNames <- predictCaptureNamesQ s
 
-        case NE.nonEmpty $ toKVs captureNames of
+        case toKVs captureNames of
             -- No named captures.  Test whether the string matches without
             -- creating any new Text values.
-            Nothing -> viewP
+            [] -> viewP
                 [e| matchesTH (Text.pack $(stringE s)) |]
                 [p| True |]
 
             -- One or more named captures.  Attempt to bind only those to local
             -- variables of the same names.
-            Just numberedNames -> viewP e p where
-                (numbers, names) = NE.unzip numberedNames
+            numberedNames -> viewP e p where
+                (numbers, names) = unzip numberedNames
                 e = [e| capturesNumberedTH
                     (Text.pack $(stringE s))
                     $(liftData numbers) |]
-                p = foldr f wildP names where
-                    f name r = conP '(:) [varP $ mkName $ Text.unpack name, r],
+                p = foldr f wildP names
+                f name r = conP '(:) [varP $ mkName $ Text.unpack name, r],
 
     quoteType = const $ fail "regex: cannot produce a type",
 
@@ -262,11 +260,11 @@
 -- > import Control.Lens
 -- > import Data.Text.Lens
 -- >
--- > embeddedNumber :: Traversal' String Int
--- > embeddedNumber = packed . [_regex|\d+|] . unpacked . _Show
+-- > embeddedNumbers :: Traversal' String Int
+-- > embeddedNumbers = packed . [_regex|\d+|] . unpacked . _Show
 -- >
 -- > main :: IO ()
--- > main = putStrLn $ "There are 14 competing standards" & embeddedNumber %~ (+ 1)
+-- > main = putStrLn $ "There are 14 competing standards" & embeddedNumbers %~ (+ 1)
 -- >
 -- > -- There are 15 competing standards
 _regex :: QuasiQuoter
diff --git a/src/hs/Text/Regex/Pcre2/Unsafe.hs b/src/hs/Text/Regex/Pcre2/Unsafe.hs
--- a/src/hs/Text/Regex/Pcre2/Unsafe.hs
+++ b/src/hs/Text/Regex/Pcre2/Unsafe.hs
@@ -8,7 +8,7 @@
 -- them here for completeness and use them to implement unit tests for this
 -- library; for ordinary use, however, seek other means to accomplish whatever
 -- is needed (such as accreting effects with optics), since they carry all the
--- problems of `unsafePerformIO`.  See
+-- problems of `System.IO.Unsafe.unsafePerformIO`.  See
 -- the [C API docs](https://pcre.org/current/doc/html/pcre2callout.html)
 -- for more information.
 module Text.Regex.Pcre2.Unsafe (
@@ -28,5 +28,4 @@
     SubCalloutResult(..))
 where
 
-import System.IO.Unsafe          (unsafePerformIO)
 import Text.Regex.Pcre2.Internal
