diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,3 +1,8 @@
+# 0.4.1
+
+* Generalized type of `pRead`.
+
+
 # 0.4
 
 * BREAKING CHANGE. `pElement` now skips leading whitespace before an element.
diff --git a/lib/Xmlbf.hs b/lib/Xmlbf.hs
--- a/lib/Xmlbf.hs
+++ b/lib/Xmlbf.hs
@@ -8,7 +8,10 @@
 -- | XML back and forth!
 --
 -- @xmlbf@ doesn't do any parsing of raw XML on its own. Instead, one should
--- rely on libraries like @xmlbf-xeno@ or @xmlbf-xmlhtml@ for this.
+-- rely on libraries like
+-- [xmlbf-xeno](https://hackage.haskell.org/package/xmlbf-xeno) or
+-- [xmlbf-xmlhtml](https://hackage.haskell.org/package/xmlbf-xmlhtml) for
+-- this.
 --
 -- @xmlbf@ provides a 'FromXml' class intended to be used as the familiar
 -- 'Data.Aeson.FromJSON' from the @aeson@ package. This relies on the
@@ -177,8 +180,8 @@
 
 -- | XML parser monad. To be run with 'runParser'.
 --
--- You can build a 'Parser' using 'element', 'pAttr', 'pAttrs', 'pText', 'pRead',
--- or any of the 'Applicative', 'Alternative' or 'Monad' combinators.
+-- You can build a 'Parser' using 'pElement', 'pAttr', 'pAttrs', 'pText',
+-- 'pRead', or any of the 'Applicative', 'Alternative' or 'Monad' combinators.
 newtype Parser a = Parser { unParser :: S -> Either String (a, S) }
   deriving (Functor)
 
@@ -296,16 +299,18 @@
     SReg as (Text x : cs) -> Right (x, SReg as cs)
     _ -> Left "Missing text node"
 
--- | Parses a value that can be 'read'.
+-- | A version of 'read' (from "Prelude") that can fail in the 'Parser'
+-- monad (or any other 'MonadFail').
 --
--- Consumes the raw string from the parser state.
-pRead :: (Typeable a, Read a) => T.Text -> Parser a
+-- Note: In case it isn't obvious from the type signature, this function doesn't
+-- touch the underlying 'Parser' state at all.
+pRead :: (Typeable a, Read a, MonadFail m) => T.Text -> m a
 {-# INLINABLE pRead #-}
 pRead = \t -> case Text.Read.readMaybe (T.unpack t) of
   Just a -> pure a
   ya@Nothing -> do
     let ty = tyConName (typeRepTyCon (typeRep ya))
-    Parser (\_ -> Left ("Can't read as " ++ ty ++ ": " ++ show t))
+    Control.Monad.Fail.fail ("Can't read as " ++ ty ++ ": " ++ show t)
 
 -- | Succeeds if all of the elements, attributes and text nodes have
 -- been consumed.
@@ -358,8 +363,9 @@
 
 -- | Post-order depth-first replacement of 'Node' and all of its children.
 --
--- This function works like 'fix', but the given function is trying to find a
--- fixpoint for the individual children nodes, not for the root node.
+-- This function works like 'Data.Function.fix', but the given function is
+-- trying to find a fixpoint for the individual children nodes, not for the root
+-- node.
 --
 -- For example, the following function renames every node named @"w"@ to @"y"@,
 -- and every node named @"y"@ to @"z"@. It accomplishes this by first renaming
@@ -377,9 +383,9 @@
 -- @
 -- foo :: 'Node' -> ['Node']
 -- foo = 'dfpos' $ \\k -> \\case
---     'Element' "w" as cs -> let 'Right' e = 'element' "x" as cs in k e
---     'Element' "x" as cs -> let 'Right' e = 'element' "y" as cs in [e]
---     'Element' "y" as cs -> let 'Right' e = 'element' "z" as cs in k e
+--     'Element' "w" as cs -> k ('element'' "x" as cs)
+--     'Element' "x" as cs -> ['element'' "y" as cs]
+--     'Element' "y" as cs -> k ('element'' "z" as cs)
 -- @
 --
 -- See 'dfpre' for pre-orderd depth-first replacement.
diff --git a/test/Test.hs b/test/Test.hs
--- a/test/Test.hs
+++ b/test/Test.hs
@@ -192,6 +192,8 @@
 
   , HU.testCase "read" $ do
       Right False @=? X.runParser (X.pRead =<< X.pText) [X.text "False"]
+      (Left "Can't read as Bool: \"XXXXX\"" :: Either String Bool)
+        @=? X.runParser (X.pRead =<< X.pText) [X.text "XXXXX"]
   ]
 
 
diff --git a/xmlbf.cabal b/xmlbf.cabal
--- a/xmlbf.cabal
+++ b/xmlbf.cabal
@@ -1,5 +1,5 @@
 name: xmlbf
-version: 0.4
+version: 0.4.1
 synopsis: XML back and forth! Parser, renderer, ToXml, FromXml, fixpoints.
 description: XML back and forth! Parser, renderer, ToXml, FromXml, fixpoints.
 homepage: https://gitlab.com/k0001/xmlbf
