diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,9 @@
 # Revision history for input-parsers
 
+## 0.2 -- 2021-03-07
+
+* Added `ParserPosition` and made `Position` a class.
+
 ## 0.1.0.1 -- 2020-07-19
 
 * Incremented the upper bound of `base` dependency.
diff --git a/input-parsers.cabal b/input-parsers.cabal
--- a/input-parsers.cabal
+++ b/input-parsers.cabal
@@ -1,6 +1,6 @@
 cabal-version:       >=1.10
 name:                input-parsers
-version:             0.1.0.1
+version:             0.2
 synopsis:            Extension of the parsers library with more capability and efficiency
 description:
                      Extended version of the parsers library, with the additional classes providing more
@@ -36,8 +36,8 @@
                        Text.Parser.Input.Position,
                        Text.Parser.Wrapper
   other-modules:       Text.Parser.Internal
-  build-depends:       base >=4.9 && <5, bytestring >=0.10 && <0.11, text >=1.2 && <1.3,
-                       monoid-subclasses >= 1.0 && < 1.1, parsers >= 0.12 && < 0.13,
+  build-depends:       base >=4.9 && <5, bytestring >=0.10 && <0.12, text >=1.2 && <1.3,
+                       monoid-subclasses >= 1.0 && < 1.2, parsers >= 0.12 && < 0.13,
                        transformers >=0.2 && <0.6
   if flag(binary)
     build-depends: binary     >= 0.7.2    && < 1
diff --git a/src/Text/Parser/Input.hs b/src/Text/Parser/Input.hs
--- a/src/Text/Parser/Input.hs
+++ b/src/Text/Parser/Input.hs
@@ -12,7 +12,7 @@
 -- | Parsers that can consume and return a prefix of their input.
 
 module Text.Parser.Input (InputParsing(..), InputCharParsing(..), ConsumedInputParsing(..),
-                          Lazy(..), Strict(..), Position) where
+                          Lazy(..), Strict(..)) where
 
 import Control.Applicative (Applicative ((<*>), pure), Alternative ((<|>), empty), (<**>))
 import Control.Monad (MonadPlus, void)
@@ -27,7 +27,7 @@
 import qualified Control.Monad.Trans.RWS.Strict as Strict (RWST(RWST))
 import Data.Functor ((<$>))
 import qualified Data.List as List
-import Data.Monoid (Monoid, mappend, mempty)
+import Data.Monoid (Monoid, Dual, mappend, mempty)
 import Data.String (IsString (fromString))
 import Text.ParserCombinators.ReadP (ReadP)
 import qualified Text.ParserCombinators.ReadP as ReadP
@@ -79,10 +79,11 @@
 class LookAheadParsing m => InputParsing m where
    -- | The type of the input stream that the parser @m@ expects to parse.
    type ParserInput m
+   type ParserPosition m
    -- | Always sucessful parser that returns the entire remaining input without consuming it.
    getInput :: m (ParserInput m)
    -- | Retrieve the 'Position' reached by the parser in the input source.
-   getSourcePos :: m Position
+   getSourcePos :: m (ParserPosition m)
 
    -- | A parser that accepts any single atomic prefix of the input stream.
    --
@@ -121,7 +122,9 @@
    -- version of 'concat' @.@ 'Control.Applicative.some' @.@ 'satisfy'.
    takeWhile1 :: (ParserInput m -> Bool) -> m (ParserInput m)
 
-   default getSourcePos :: (FactorialMonoid (ParserInput m), Functor m) => m Position
+   type ParserPosition m = Dual Int
+   default getSourcePos :: (FactorialMonoid (ParserInput m), Functor m, ParserPosition m ~ Dual Int)
+                        => m (ParserPosition m)
    getSourcePos = fromEnd . Factorial.length <$> getInput
    anyToken = take 1
    default satisfy :: Monad m => (ParserInput m -> Bool) -> m (ParserInput m)
@@ -202,6 +205,7 @@
 
 instance (Monad m, InputParsing m) => InputParsing (IdentityT m) where
    type ParserInput (IdentityT m) = ParserInput m
+   type ParserPosition (IdentityT m) = ParserPosition m
    getInput = IdentityT getInput
    getSourcePos = IdentityT getSourcePos
    anyToken = IdentityT anyToken
@@ -225,6 +229,7 @@
 
 instance (MonadPlus m, InputParsing m) => InputParsing (ReaderT e m) where
    type ParserInput (ReaderT e m) = ParserInput m
+   type ParserPosition (ReaderT e m) = ParserPosition m
    getInput = lift getInput
    getSourcePos = lift getSourcePos
    anyToken = lift anyToken
@@ -248,6 +253,7 @@
 
 instance (MonadPlus m, InputParsing m, Monoid w) => InputParsing (Lazy.WriterT w m) where
    type ParserInput (Lazy.WriterT w m) = ParserInput m
+   type ParserPosition (Lazy.WriterT w m) = ParserPosition m
    getInput = lift getInput
    getSourcePos = lift getSourcePos
    anyToken = lift anyToken
@@ -271,6 +277,7 @@
 
 instance (MonadPlus m, InputParsing m, Monoid w) => InputParsing (Strict.WriterT w m) where
    type ParserInput (Strict.WriterT w m) = ParserInput m
+   type ParserPosition (Strict.WriterT w m) = ParserPosition m
    getInput = lift getInput
    getSourcePos = lift getSourcePos
    anyToken = lift anyToken
@@ -294,6 +301,7 @@
 
 instance (MonadPlus m, InputParsing m) => InputParsing (Lazy.StateT s m) where
    type ParserInput (Lazy.StateT s m) = ParserInput m
+   type ParserPosition (Lazy.StateT s m) = ParserPosition m
    getInput = lift getInput
    getSourcePos = lift getSourcePos
    anyToken = lift anyToken
@@ -317,6 +325,7 @@
 
 instance (MonadPlus m, InputParsing m) => InputParsing (Strict.StateT s m) where
    type ParserInput (Strict.StateT s m) = ParserInput m
+   type ParserPosition (Strict.StateT s m) = ParserPosition m
    getInput = lift getInput
    getSourcePos = lift getSourcePos
    anyToken = lift anyToken
@@ -340,6 +349,7 @@
 
 instance (MonadPlus m, InputParsing m, Monoid w) => InputParsing (Lazy.RWST r w s m) where
    type ParserInput (Lazy.RWST r w s m) = ParserInput m
+   type ParserPosition (Lazy.RWST r w s m) = ParserPosition m
    getInput = lift getInput
    getSourcePos = lift getSourcePos
    anyToken = lift anyToken
@@ -363,6 +373,7 @@
 
 instance (MonadPlus m, InputParsing m, Monoid w) => InputParsing (Strict.RWST r w s m) where
    type ParserInput (Strict.RWST r w s m) = ParserInput m
+   type ParserPosition (Strict.RWST r w s m) = ParserPosition m
    getInput = lift getInput
    getSourcePos = lift getSourcePos
    anyToken = lift anyToken
@@ -448,6 +459,7 @@
 #ifdef MIN_VERSION_binary
 instance InputParsing (Lazy Binary.Get) where
    type ParserInput (Lazy Binary.Get) = Lazy.ByteString
+   type ParserPosition (Lazy Binary.Get) = Int
    getInput = Lazy (Binary.lookAhead Binary.getRemainingLazyByteString)
    getSourcePos = Lazy (fromStart . fromIntegral <$> Binary.bytesRead)
    anyToken = Lazy (Binary.getLazyByteString 1)
@@ -455,6 +467,7 @@
 
 instance InputParsing (Strict Binary.Get) where
    type ParserInput (Strict Binary.Get) = ByteString
+   type ParserPosition (Strict Binary.Get) = Int
    getInput = Strict (Lazy.toStrict <$> Binary.lookAhead Binary.getRemainingLazyByteString)
    getSourcePos = Strict (fromStart . fromIntegral <$> Binary.bytesRead)
    anyToken = Strict (Binary.getByteString 1)
diff --git a/src/Text/Parser/Input/Position.hs b/src/Text/Parser/Input/Position.hs
--- a/src/Text/Parser/Input/Position.hs
+++ b/src/Text/Parser/Input/Position.hs
@@ -7,37 +7,44 @@
 
 import Data.Char (isSpace)
 import Data.String (IsString(fromString))
+import Data.Monoid (Dual(Dual))
 import qualified Data.Monoid.Factorial as Factorial
 import qualified Data.Monoid.Textual as Textual
 import Data.Monoid.Factorial (FactorialMonoid)
 import Data.Monoid.Textual (TextualMonoid)
 
--- | Opaque data type that represents an input position.
-data Position = PositionFromStart !Int
-                -- ^ the length of the input from the start to the position
-              | PositionFromEnd Int
-                -- ^ the length of the input from the position to end
-              deriving (Eq, Read, Show)
+-- | A class for representing position values.
+--
+-- > move (distance pos1 pos2) pos1 == pos2
+class Position p where
+   -- | Distance from the first position to the second
+   distance :: p -> p -> Int
+   -- | Move the position by the given distance.
+   move :: Int -> p -> p
+   -- | Map the position into its offset from the beginning of the full input.
+   offset :: FactorialMonoid s => s -> p -> Int
 
+instance Position Int where
+   distance = flip (-)
+   move = (+)
+   offset = const id
+
+instance Position a => Position (Dual a) where
+   distance (Dual p1) (Dual p2) = distance p2 p1
+   move distance (Dual p) = Dual (move (negate distance) p)
+   offset wholeInput (Dual p) = Factorial.length wholeInput - offset wholeInput p
+
 -- | Construct a 'Position' given the offset from the beginning of the full input.
-fromStart :: Int -> Position
-fromStart = PositionFromStart
+fromStart :: Int -> Int
+fromStart = id
 
 -- | Construct a 'Position' given the length remaining from the position to the end of the input.
-fromEnd :: Int -> Position
-fromEnd = PositionFromEnd
-
--- | Map the position into its offset from the beginning of the full input.
---
--- > offset input . fromStart === id
-offset :: FactorialMonoid s => s -> Position -> Int
-offset wholeInput (PositionFromStart offset) = offset
-offset wholeInput (PositionFromEnd remainderLength) = Factorial.length wholeInput - remainderLength
-{-# INLINE offset #-}
+fromEnd :: Int -> Dual Int
+fromEnd = Dual
 
 -- | Given the parser input, a 'Position' within it, and desired number of context lines, returns a description of
 -- the offset position in English.
-context :: (Eq s, TextualMonoid s) => s -> Position -> Int -> s
+context :: (Eq s, TextualMonoid s, Position p) => s -> p -> Int -> s
 context input pos contextLineCount = 
    foldMap (<> "\n") prevLines <> lastLinePadding
    <> "at line " <> fromString (show $ length allPrevLines) <> ", column " <> fromString (show $ column+1) <> "\n"
@@ -50,7 +57,7 @@
 
 -- | Given the full input and an offset within it, returns all the input lines up to and including the offset
 -- in reverse order, as well as the zero-based column number of the offset
-lineAndColumn :: (Eq s, IsString s, FactorialMonoid s) => s -> Position -> ([s], Int)
+lineAndColumn :: (Eq s, IsString s, FactorialMonoid s, Position p) => s -> p -> ([s], Int)
 lineAndColumn input pos = context [] (offset input pos) (Factorial.split (== "\n") input)
   where context revLines restCount []
           | restCount > 0 = (["Error: the offset is beyond the input length"], -1)
