diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,5 +1,8 @@
 # Changelog and Acknowledgements
 
+## 2.0.4
+* Added `Show` instance for `Captures` to ease debugging user code.
+
 ## 2.0.3
 * Updated PCRE2 to 10.39 (no API changes).  The C sources are now drawn from
   https://github.com/PhilipHazel/pcre2.git, which fixes
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.3
+version:        2.0.4
 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
diff --git a/src/hs/Text/Regex/Pcre2/Foreign/TH.hs b/src/hs/Text/Regex/Pcre2/Foreign/TH.hs
--- a/src/hs/Text/Regex/Pcre2/Foreign/TH.hs
+++ b/src/hs/Text/Regex/Pcre2/Foreign/TH.hs
@@ -4,7 +4,7 @@
 
 {-
     For example,
-constant "ERROR_NOMATCH" ''CInt
+constant ''CInt "ERROR_NOMATCH"
     will produce
 foreign import capi unsafe "pcre2.h value PCRE2_ERROR_NOMATCH"
     pcre2_ERROR_NOMATCH :: CInt
@@ -18,7 +18,7 @@
 
 {-
     For example,
-getter "callout_block" "version" ''CUInt
+getter "callout_block" ''CUInt "version"
     will produce
 foreign import capi unsafe "getters.h" pcre2_callout_block_version
     :: Ptr Pcre2_callout_block
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
@@ -539,8 +539,8 @@
         MatchContextOption pcre2_set_offset_limit (fromIntegral limit)
 
     where
-    unary ctor f x = (: []) $ ctor $ \ctx -> withForeignPtr ctx $ \ctxPtr ->
-        f ctxPtr x >>= check (== 0)
+    unary ctor f x = [ctor $ \ctx -> withForeignPtr ctx applyAndCheck] where
+        applyAndCheck ctxPtr = f ctxPtr x >>= check (== 0)
 
 -- | Intermediate representation of options expressing what effect they\'ll have
 -- on which stage of regex compilation\/execution.  Also provide fake @Prism'@s.
@@ -1053,7 +1053,7 @@
 --
 -- * `Proxy` when we are only checking if a match succeeded;
 --
--- * `[]` when we want to easily pattern match specific capture groups via
+-- * @[]@ when we want to easily pattern match specific capture groups via
 --   Template Haskell-generated @ViewPatterns@.
 type FromMatch t = Ptr Pcre2_match_data -> IO (t Slice)
 
@@ -1064,9 +1064,9 @@
     let peekOvec :: Int -> IO Text.I16
         peekOvec = fmap fromIntegral . peekElemOff ovecPtr
 
-    forM whitelist $ \i -> liftM2 Slice
-        (peekOvec $ i * 2)
-        (peekOvec $ i * 2 + 1)
+    forM whitelist $ \i -> Slice
+        <$> peekOvec (i * 2)
+        <*> peekOvec (i * 2 + 1)
 
 -- | Read just the 0th capture\'s offsets from match results.
 get0thSlice :: FromMatch Identity
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
@@ -45,6 +45,7 @@
 -- level and having to write signatures.  In times of need, \"@Captures _@\" may
 -- be written with the help of @{-\# LANGUAGE PartialTypeSignatures \#-}@.
 newtype Captures (info :: CapturesInfo) = Captures (NonEmpty Text)
+    deriving (Show)
 
 -- | 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
diff --git a/test/Spec.hs b/test/Spec.hs
--- a/test/Spec.hs
+++ b/test/Spec.hs
@@ -188,6 +188,11 @@
                     & embeddedNumber %~ (+ 1)
             result `shouldBe` "There are 15 competing standards"
 
+    describe "Captures" $ do
+        it "have a predictable Show instance" $ do
+            cs <- [regex|a(b)(?<c>c)|] "abc"
+            show cs `shouldBe` "Captures (\"abc\" :| [\"b\",\"c\"])"
+
     describe "an unset capture" $ do
         it "is treated as empty" $ do
             captures "(a)?" "" `shouldBe` Just ("" :| [""])
