diff --git a/paripari.cabal b/paripari.cabal
--- a/paripari.cabal
+++ b/paripari.cabal
@@ -2,12 +2,12 @@
 --
 -- see: https://github.com/sol/hpack
 --
--- hash: 78092995723d296e73be8c5403a8bf6c7b46bece1826eb1e0841683e3252071d
+-- hash: 9f4767161c726109fa8849cc08f350e49f03eeef59675e9c770da1f6bd959ab8
 
 name:           paripari
-version:        0.3.0.0
+version:        0.4.0.0
 synopsis:       Parser combinators with fast-path and slower fallback for error reporting
-description:    PariPari offers two parsing strategies. There is a fast Acceptor and a slower Reporter which are evaluated in parallel. If the Acceptor fails, the Reporter returns a report about the parsing errors. Unlike Parsec and like Attoparsec, the parser combinators backtrack by default.
+description:    PariPari offers two parsing strategies. There is a fast Acceptor and a slower Reporter which are evaluated in parallel. If the Acceptor fails, the Reporter returns a report about the parsing errors. Like Attoparsec, the parser combinators backtrack by default.
 category:       Text
 stability:      experimental
 homepage:       https://github.com/minad/paripari#readme
@@ -17,7 +17,7 @@
 copyright:      2018 Daniel Mendler
 license:        MIT
 license-file:   LICENSE
-tested-with:    GHC == 8.4.3, GHC == 8.6.1
+tested-with:    GHC == 8.0.1, GHC == 8.2.1, GHC == 8.4.3, GHC == 8.6.1
 build-type:     Simple
 cabal-version:  >= 1.10
 
@@ -40,7 +40,7 @@
       Paths_paripari
   hs-source-dirs:
       src
-  ghc-options: -Wall -Widentities -Wmonomorphism-restriction -Wincomplete-uni-patterns -Wincomplete-record-updates -Wtabs -fprint-potential-instances
+  ghc-options: -Wall -Wcompat -Widentities -Wmonomorphism-restriction -Wincomplete-uni-patterns -Wincomplete-record-updates -Wtabs -fprint-potential-instances
   build-depends:
       base >=4.8 && <5
     , bytestring >=0.10 && <0.11
@@ -52,7 +52,7 @@
   main-is: example.hs
   other-modules:
       Paths_paripari
-  ghc-options: -Wall -Widentities -Wmonomorphism-restriction -Wincomplete-uni-patterns -Wincomplete-record-updates -Wtabs -fprint-potential-instances
+  ghc-options: -Wall -Wcompat -Widentities -Wmonomorphism-restriction -Wincomplete-uni-patterns -Wincomplete-record-updates -Wtabs -fprint-potential-instances
   build-depends:
       base >=4.8 && <5
     , bytestring >=0.10 && <0.11
@@ -65,7 +65,7 @@
   main-is: specialise-all.hs
   other-modules:
       Paths_paripari
-  ghc-options: -Wall -Widentities -Wmonomorphism-restriction -Wincomplete-uni-patterns -Wincomplete-record-updates -Wtabs -fprint-potential-instances
+  ghc-options: -Wall -Wcompat -Widentities -Wmonomorphism-restriction -Wincomplete-uni-patterns -Wincomplete-record-updates -Wtabs -fprint-potential-instances
   build-depends:
       base >=4.8 && <5
     , bytestring >=0.10 && <0.11
@@ -81,7 +81,7 @@
       Paths_paripari
   hs-source-dirs:
       test
-  ghc-options: -Wall -Widentities -Wmonomorphism-restriction -Wincomplete-uni-patterns -Wincomplete-record-updates -Wtabs -fprint-potential-instances
+  ghc-options: -Wall -Wcompat -Widentities -Wmonomorphism-restriction -Wincomplete-uni-patterns -Wincomplete-record-updates -Wtabs -fprint-potential-instances
   build-depends:
       base >=4.8 && <5
     , bytestring >=0.10 && <0.11
diff --git a/specialise-all.hs b/specialise-all.hs
--- a/specialise-all.hs
+++ b/specialise-all.hs
@@ -4,6 +4,7 @@
 {-# LANGUAGE Rank2Types #-}
 
 import System.Environment (getArgs)
+import Data.Semigroup ((<>))
 import Text.PariPari
 import qualified Data.Char as C
 import qualified Data.List.NonEmpty as NE
diff --git a/src/Text/PariPari/Internal/Acceptor.hs b/src/Text/PariPari/Internal/Acceptor.hs
--- a/src/Text/PariPari/Internal/Acceptor.hs
+++ b/src/Text/PariPari/Internal/Acceptor.hs
@@ -15,6 +15,7 @@
 ) where
 
 import Control.Monad (void)
+import Data.Semigroup as Sem
 import Data.String (IsString(..))
 import Text.PariPari.Internal.Chunk
 import Text.PariPari.Internal.Class
@@ -43,14 +44,17 @@
                -> b
   }
 
-instance (Chunk k, Semigroup a) => Semigroup (Acceptor k a) where
+instance (Chunk k, Semigroup a) => Sem.Semigroup (Acceptor k a) where
   p1 <> p2 = (<>) <$> p1 <*> p2
   {-# INLINE (<>) #-}
 
-instance (Chunk k, Monoid a) => Monoid (Acceptor k a) where
+instance (Chunk k, Semigroup a, Monoid a) => Monoid (Acceptor k a) where
   mempty = pure mempty
   {-# INLINE mempty #-}
 
+  mappend = (<>)
+  {-# INLINE mappend #-}
+
 instance Functor (Acceptor k) where
   fmap f p = Acceptor $ \env st ok err ->
     unAcceptor p env st (ok . f) err
@@ -212,19 +216,19 @@
           err $ ECombinator "char"
   {-# INLINE char #-}
 
-  asciiSatisfy f = Acceptor $ \env st@State{_stOff, _stLine, _stCol} ok err ->
+  asciiScan f = Acceptor $ \env st@State{_stOff, _stLine, _stCol} ok err ->
     if | b <- byteAt @k (_envBuf env) _stOff,
          b /= 0,
          b < 128,
-         f b ->
-           ok b st
+         Just x <- f b ->
+           ok x st
            { _stOff = _stOff + 1
            , _stLine = if b == asc_newline then _stLine + 1 else _stLine
            , _stCol = if b == asc_newline then 1 else _stCol + 1
            }
        | otherwise ->
-           err $ ECombinator "asciiSatisfy"
-  {-# INLINE asciiSatisfy #-}
+           err $ ECombinator "asciiScan"
+  {-# INLINE asciiScan #-}
 
   asciiByte 0 = error "Character '\\0' cannot be parsed because it is used as sentinel"
   asciiByte b
diff --git a/src/Text/PariPari/Internal/CharCombinators.hs b/src/Text/PariPari/Internal/CharCombinators.hs
--- a/src/Text/PariPari/Internal/CharCombinators.hs
+++ b/src/Text/PariPari/Internal/CharCombinators.hs
@@ -27,14 +27,15 @@
   , spaceChar
   , asciiChar
   , satisfy
+  , asciiSatisfy
   , skipChars
   , takeChars
   , skipCharsWhile
   , takeCharsWhile
   , skipCharsWhile1
   , takeCharsWhile1
-  , scanCharsWhile
-  , scanCharsWhile1
+  , scanChars
+  , scanChars1
   , string
 ) where
 
@@ -42,6 +43,7 @@
 import Control.Monad.Combinators (option, skipCount, skipMany)
 import Data.Functor (void)
 import Data.Maybe (fromMaybe)
+import Data.Semigroup ((<>))
 import Data.Word (Word8)
 import Text.PariPari.Internal.Chunk
 import Text.PariPari.Internal.Class
@@ -303,11 +305,16 @@
 satisfy f = scan $ \c -> if f c then Just c else Nothing
 {-# INLINE satisfy #-}
 
-scanCharsWhile :: CharParser k p => (s -> Char -> Maybe s) -> s -> p s
-scanCharsWhile f = go
+-- | Parse a single character within the ASCII charset with the given predicate
+asciiSatisfy :: CharParser k p => (Word8 -> Bool) -> p Word8
+asciiSatisfy f = asciiScan $ \b -> if f b then Just b else Nothing
+{-# INLINE asciiSatisfy #-}
+
+scanChars :: CharParser k p => (s -> Char -> Maybe s) -> s -> p s
+scanChars f = go
   where go s = (scan (f s) >>= go) <|> pure s
-{-# INLINE scanCharsWhile #-}
+{-# INLINE scanChars #-}
 
-scanCharsWhile1 :: CharParser k p => (s -> Char -> Maybe s) -> s -> p s
-scanCharsWhile1 f s = scan (f s) >>= scanCharsWhile f
-{-# INLINE scanCharsWhile1 #-}
+scanChars1 :: CharParser k p => (s -> Char -> Maybe s) -> s -> p s
+scanChars1 f s = scan (f s) >>= scanChars f
+{-# INLINE scanChars1 #-}
diff --git a/src/Text/PariPari/Internal/Chunk.hs b/src/Text/PariPari/Internal/Chunk.hs
--- a/src/Text/PariPari/Internal/Chunk.hs
+++ b/src/Text/PariPari/Internal/Chunk.hs
@@ -16,6 +16,7 @@
 import Data.Bits (unsafeShiftL, (.|.), (.&.))
 import Data.ByteString (ByteString)
 import Data.Foldable (foldl')
+import Data.Semigroup ((<>))
 import Data.String (fromString)
 import Data.Text (Text)
 import Data.Word (Word8)
diff --git a/src/Text/PariPari/Internal/Class.hs b/src/Text/PariPari/Internal/Class.hs
--- a/src/Text/PariPari/Internal/Class.hs
+++ b/src/Text/PariPari/Internal/Class.hs
@@ -17,6 +17,7 @@
 import Control.Monad (MonadPlus(..))
 import Control.Monad.Fail (MonadFail(..))
 import Data.List (intercalate)
+import Data.Semigroup ((<>))
 import Data.String (IsString)
 import Data.Word (Word8)
 import GHC.Generics (Generic)
@@ -89,7 +90,7 @@
   -- | Parse a single element
   element :: Element k -> p (Element k)
 
-  -- | Scan a single byte
+  -- | Scan a single element
   elementScan :: (Element k -> Maybe a) -> p a
 
   -- | Parse a chunk of elements. The chunk must not
@@ -120,11 +121,11 @@
   -- since it is used as decoding sentinel. Use 'element' instead.
   asciiByte :: Word8 -> p Word8
 
-  -- | Parse a single character within the ASCII charset with the given predicate
+  -- | Scan a single character within the ASCII charset
   --
   -- __Note__: The character '\0' cannot be parsed using this combinator
-  -- since it is used as decoding sentinel. Use 'elementSatisfy' instead.
-  asciiSatisfy :: (Word8 -> Bool) -> p Word8
+  -- since it is used as decoding sentinel. Use 'elementScan' instead.
+  asciiScan :: (Word8 -> Maybe a) -> p a
 
 -- | Pretty string representation of 'Error'
 showError :: Error -> String
diff --git a/src/Text/PariPari/Internal/ElementCombinators.hs b/src/Text/PariPari/Internal/ElementCombinators.hs
--- a/src/Text/PariPari/Internal/ElementCombinators.hs
+++ b/src/Text/PariPari/Internal/ElementCombinators.hs
@@ -55,14 +55,15 @@
   , takeElementsWhile
   , skipElementsWhile1
   , takeElementsWhile1
-  , scanElementsWhile
-  , scanElementsWhile1
+  , scanElements
+  , scanElements1
 ) where
 
 import Control.Applicative ((<|>), empty)
 import Control.Monad (when)
 import Control.Monad.Combinators (skipCount, skipMany)
 import Data.Functor (void)
+import Data.Semigroup ((<>))
 import Prelude hiding (getLine)
 import Text.PariPari.Internal.Chunk
 import Text.PariPari.Internal.Class
@@ -146,12 +147,12 @@
 linefold = line <|> indented
 {-# INLINE linefold #-}
 
--- | Parser a single byte different from the given one
+-- | Parser a single element different from the given one
 notElement :: forall k p. ChunkParser k p => Element k -> p (Element k)
 notElement e = elementSatisfy @k (/= e) <?> "not " <> showElement @k e
 {-# INLINE notElement #-}
 
--- | Parse an arbitrary byte
+-- | Parse an arbitrary element
 anyElement :: ChunkP k (Element k)
 anyElement = elementSatisfy (const True)
 {-# INLINE anyElement #-}
@@ -186,16 +187,16 @@
 takeElementsWhile1 f = asChunk (skipElementsWhile1 f)
 {-# INLINE takeElementsWhile1 #-}
 
--- | Parse a single byte with the given predicate
+-- | Parse a single element with the given predicate
 elementSatisfy :: ChunkParser k p => (Element k -> Bool) -> p (Element k)
 elementSatisfy f = elementScan $ \e -> if f e then Just e else Nothing
 {-# INLINE elementSatisfy #-}
 
-scanElementsWhile :: ChunkParser k p => (s -> Element k -> Maybe s) -> s -> p s
-scanElementsWhile f = go
+scanElements :: ChunkParser k p => (s -> Element k -> Maybe s) -> s -> p s
+scanElements f = go
   where go s = (elementScan (f s) >>= go) <|> pure s
-{-# INLINE scanElementsWhile #-}
+{-# INLINE scanElements #-}
 
-scanElementsWhile1 :: ChunkParser k p => (s -> Element k -> Maybe s) -> s -> p s
-scanElementsWhile1 f s = elementScan (f s) >>= scanElementsWhile f
-{-# INLINE scanElementsWhile1 #-}
+scanElements1 :: ChunkParser k p => (s -> Element k -> Maybe s) -> s -> p s
+scanElements1 f s = elementScan (f s) >>= scanElements f
+{-# INLINE scanElements1 #-}
diff --git a/src/Text/PariPari/Internal/Reporter.hs b/src/Text/PariPari/Internal/Reporter.hs
--- a/src/Text/PariPari/Internal/Reporter.hs
+++ b/src/Text/PariPari/Internal/Reporter.hs
@@ -28,6 +28,7 @@
 import Data.Function (on)
 import Data.List (intercalate, sort, group, sortOn)
 import Data.List.NonEmpty (NonEmpty(..))
+import Data.Semigroup as Sem
 import Data.String (IsString(..))
 import GHC.Generics (Generic)
 import Text.PariPari.Internal.Chunk
@@ -86,14 +87,17 @@
                -> b
   }
 
-instance (Chunk k, Semigroup a) => Semigroup (Reporter k a) where
+instance (Chunk k, Semigroup a) => Sem.Semigroup (Reporter k a) where
   p1 <> p2 = (<>) <$> p1 <*> p2
   {-# INLINE (<>) #-}
 
-instance (Chunk k, Monoid a) => Monoid (Reporter k a) where
+instance (Chunk k, Semigroup a, Monoid a) => Monoid (Reporter k a) where
   mempty = pure mempty
   {-# INLINE mempty #-}
 
+  mappend = (<>)
+  {-# INLINE mappend #-}
+
 instance Chunk k => Functor (Reporter k) where
   fmap f p = Reporter $ \env st ok err ->
     unReporter p env st (ok . f) err
@@ -262,12 +266,12 @@
           raiseError env st err $ EExpected [show c]
   {-# INLINE char #-}
 
-  asciiSatisfy f = Reporter $ \env st@State{_stOff, _stLine, _stCol} ok err ->
+  asciiScan f = Reporter $ \env st@State{_stOff, _stLine, _stCol} ok err ->
     let b = byteAt @k (_envBuf env) _stOff
     in if | b /= 0,
             b < 128,
-            f b ->
-              ok b st
+            Just x <- f b ->
+              ok x st
               { _stOff = _stOff + 1
               , _stLine = if b == asc_newline then _stLine + 1 else _stLine
               , _stCol = if b == asc_newline then 1 else _stCol + 1
@@ -276,7 +280,7 @@
               raiseError env st err unexpectedEnd
           | otherwise ->
               raiseError env st err $ EUnexpected $ showByte b
-  {-# INLINE asciiSatisfy #-}
+  {-# INLINE asciiScan #-}
 
   asciiByte 0 = error "Character '\\0' cannot be parsed because it is used as sentinel"
   asciiByte b
diff --git a/src/Text/PariPari/Internal/Tracer.hs b/src/Text/PariPari/Internal/Tracer.hs
--- a/src/Text/PariPari/Internal/Tracer.hs
+++ b/src/Text/PariPari/Internal/Tracer.hs
@@ -9,6 +9,7 @@
   , runTracer
 ) where
 
+import Data.Semigroup as Sem
 import Data.String (IsString)
 import Debug.Trace (trace)
 import Text.PariPari.Internal.Chunk
@@ -18,7 +19,7 @@
 
 -- | Parser which prints trace messages, when backtracking occurs.
 newtype Tracer k a = Tracer { unTracer :: Reporter k a }
-  deriving (Semigroup, Monoid, Functor, Applicative, MonadPlus, Monad, Fail.MonadFail)
+  deriving (Sem.Semigroup, Monoid, Functor, Applicative, MonadPlus, Monad, Fail.MonadFail)
 
 deriving instance CharChunk k => ChunkParser k (Tracer k)
 deriving instance CharChunk k => CharParser k (Tracer k)
diff --git a/test/test.hs b/test/test.hs
--- a/test/test.hs
+++ b/test/test.hs
@@ -82,20 +82,26 @@
         s <- randomString
         ok (traverse char s *> eof) s ()
 
-    , testCase "asciiSatisfy" $ do
-        ok (asciiSatisfy (== asc_a)) "abc" asc_a
-        ok (asciiSatisfy (== asc_a) <* eof) "a" asc_a
-        err (asciiSatisfy (== asc_0)) "abc"
-        err (asciiSatisfy (== asc_0)) ""
+    , testCase "scan" $ do
+        ok (scan (\c -> if c == 'a' then Just c else Nothing)) "abc" 'a'
+        ok (scan (\c -> if c == 'a' then Just c else Nothing) <* eof) "a" 'a'
+        err (scan (\c -> if c == 'b' then Just c else Nothing)) "abc"
+        err (scan (\c -> if c == 'a' then Just c else Nothing)) ""
 
         -- because of sentinel
-        err (asciiSatisfy (== 0)) "\0"
-        err (asciiSatisfy (== 0)) ""
+        err (scan (\c -> if c == '\0' then Just c else Nothing)) "\0"
+        err (scan (\c -> if c == '\0' then Just c else Nothing)) ""
 
-    , testCase "asciiSatisfy-random" $ replicateM_ randomTries $ do
-        s <- randomAsciiString
-        ok (traverse (asciiSatisfy . (==) . fromIntegral . C.ord) s *> eof) s ()
+    , testCase "asciiScan" $ do
+        ok (asciiScan (\c -> if c == asc_a then Just c else Nothing)) "abc" asc_a
+        ok (asciiScan (\c -> if c == asc_a then Just c else Nothing) <* eof) "a" asc_a
+        err (asciiScan (\c -> if c == asc_0 then Just c else Nothing)) "abc"
+        err (asciiScan (\c -> if c == asc_0 then Just c else Nothing)) ""
 
+        -- because of sentinel
+        err (asciiScan (\c -> if c == 0 then Just c else Nothing)) "\0"
+        err (asciiScan (\c -> if c == 0 then Just c else Nothing)) ""
+
     , testCase "asciiByte" $ do
         ok (asciiByte asc_a) "abc" asc_a
         ok (asciiByte asc_a <* eof) "a" asc_a
@@ -123,6 +129,20 @@
         s <- randomString
         ok (traverse (satisfy . (==)) s *> eof) s ()
 
+    , testCase "asciiSatisfy" $ do
+        ok (asciiSatisfy (== asc_a)) "abc" asc_a
+        ok (asciiSatisfy (== asc_a) <* eof) "a" asc_a
+        err (asciiSatisfy (== asc_0)) "abc"
+        err (asciiSatisfy (== asc_0)) ""
+
+        -- because of sentinel
+        err (asciiSatisfy (== 0)) "\0"
+        err (asciiSatisfy (== 0)) ""
+
+    , testCase "asciiSatisfy-random" $ replicateM_ randomTries $ do
+        s <- randomAsciiString
+        ok (traverse (asciiSatisfy . (==) . fromIntegral . C.ord) s *> eof) s ()
+
     , testCase "string" $ do
         ok (string "ab") "abc" (stringToChunk "ab")
         err (string "bc") "abc"
@@ -458,6 +478,16 @@
         ok (element '\0' <* eof) "\0" '\0'
         err (element '\0') ""
 
+    , testCase "elementScan" $ do
+        ok (elementScan (\c -> if c == 'a' then Just c else Nothing)) "abc" 'a'
+        ok (elementScan (\c -> if c == 'a' then Just c else Nothing) <* eof) "a" 'a'
+        err (elementScan (\c -> if c == 'b' then Just c else Nothing)) "abc"
+        err (elementScan Just) ""
+
+        -- sentinel
+        ok (elementScan (\c -> if c == '\0' then Just c else Nothing)) "\0" '\0'
+        ok (elementScan (\c -> if c == '\0' then Just c else Nothing) <* eof) "\0" '\0'
+
     , testCase "chunk" $ do
         ok (chunk "ab") "abc" "ab"
         err (chunk "bc") "abc"
@@ -653,17 +683,11 @@
 fraction
 fractionHex
 
-CharParser:
-scan
-
-ChunkParser:
-elementScan
-
 Element Combinators:
-scanElementsWhile
-scanElementsWhile1
+scanElements
+scanElements1
 
 Char Combinators:
-scanCharsWhile
-scanCharsWhile1
+scanChars
+scanChars1
 -}
