diff --git a/ip.cabal b/ip.cabal
--- a/ip.cabal
+++ b/ip.cabal
@@ -1,6 +1,6 @@
 cabal-version: 2.0
 name: ip
-version: 1.5.0
+version: 1.5.1
 synopsis: Library for IP and MAC addresses
 homepage: https://github.com/andrewthad/haskell-ip#readme
 license: BSD3
@@ -50,9 +50,9 @@
     , aeson >= 1.0 && < 1.5
     , attoparsec >= 0.13 && < 0.14
     , bytestring >= 0.10 && < 0.11
-    , deepseq >= 1.4 && < 1.5 
-    , hashable >= 1.2 && < 1.3
-    , primitive >= 0.6 && < 0.7
+    , deepseq >= 1.4 && < 1.5
+    , hashable >= 1.2 && < 1.4
+    , primitive >= 0.6.4 && < 0.8
     , text >= 1.2  && < 1.3
     , vector >= 0.11 && < 0.13
     , wide-word >= 0.1.0.8 && < 0.2
diff --git a/src/Net/IP.hs b/src/Net/IP.hs
--- a/src/Net/IP.hs
+++ b/src/Net/IP.hs
@@ -17,9 +17,9 @@
     All functions and instance methods that deal with textual conversion
     will encode an 'IP' using either dot-decimal notation (for IPv4) or
     RFC 5952 (for IPv6). They will decode an 'IP' from either format
-    as well. The 'Show' instance presents an address in as valid haskell code 
+    as well. The 'Show' instance presents an address in as valid haskell code
     that resembles the formatted address:
-    
+
     >>> decode "192.168.3.100"
     Just (ipv4 192 168 3 100)
     >>> decode "A3F5:12:F26::1466:8B91"
@@ -29,6 +29,8 @@
 module Net.IP
   ( -- * Pattern Matching
     case_
+  , isIPv4
+  , isIPv6
     -- * Construction
   , ipv4
   , ipv6
@@ -44,23 +46,26 @@
   , IP(..)
   ) where
 
-import Prelude hiding (print)
 import Control.DeepSeq (NFData)
-import Data.Bits
-import GHC.Generics (Generic)
-import Net.IPv6 (IPv6(..))
-import Net.IPv4 (IPv4(..))
-import Text.Read (Read(..))
-import Text.ParserCombinators.ReadPrec ((+++))
 import Data.Aeson (FromJSON(..),ToJSON(..))
+import Data.Bits
 import Data.Text (Text)
 import Data.WideWord (Word128(..))
 import Data.Word (Word8,Word16)
-import qualified Net.IPv4 as IPv4
-import qualified Net.IPv6 as IPv6
+import GHC.Generics (Generic)
+import Net.IPv4 (IPv4(..))
+import Net.IPv6 (IPv6(..))
+import Prelude hiding (print)
+import Text.ParserCombinators.ReadPrec ((+++))
+import Text.Read (Read(..))
 import qualified Data.Aeson as Aeson
 import qualified Data.Text.IO as TIO
+import qualified Net.IPv4 as IPv4
+import qualified Net.IPv6 as IPv6
 
+-- $setup
+-- >>> :set -XOverloadedStrings
+
 -- | Run a function over an 'IP' depending on its status
 --   as an 'IPv4' or 'IPv6'.
 --
@@ -99,10 +104,28 @@
 fromIPv6 = IP
 
 -- | Encode an 'IP' as 'Text'.
+--
+--   >>> encode (ipv4 10 0 0 25)
+--   "10.0.0.25"
+--
+--   >>> encode (ipv6 0x3124 0x0 0x0 0xDEAD 0xCAFE 0xFF 0xFE00 0x1)
+--   "3124::dead:cafe:ff:fe00:1"
 encode :: IP -> Text
 encode = case_ IPv4.encode IPv6.encode
 
 -- | Decode an 'IP' from 'Text'.
+--
+--   >>> decode "10.0.0.25"
+--   Just (ipv4 10 0 0 25)
+--
+--   >>> fmap isIPv4 (decode "10.0.0.25")
+--   Just True
+--
+--   >>> decode "3124::dead:cafe:ff:fe00:1"
+--   Just (ipv6 0x3124 0x0000 0x0000 0xdead 0xcafe 0x00ff 0xfe00 0x0001)
+--
+--   >>> fmap isIPv6 (decode "3124::dead:cafe:ff:fe00:1")
+--   Just True
 decode :: Text -> Maybe IP
 decode t = case IPv4.decode t of
   Nothing -> case IPv6.decode t of
@@ -110,7 +133,36 @@
     Just v6 -> Just (fromIPv6 v6)
   Just v4 -> Just (fromIPv4 v4)
 
--- | Print an 'IP' using the textual encoding.
+-- | Is the 'IP' an IPv4 address?
+--
+--   >>> isIPv4 (ipv4 10 0 0 25)
+--   True
+--
+--   >>> isIPv4 (ipv6 0x3124 0x0 0x0 0xDEAD 0xCAFE 0xFF 0xFE00 0x1)
+--   False
+isIPv4 :: IP -> Bool
+isIPv4 = case_ (const True) (const False)
+{-# inline isIPv4 #-}
+
+-- | Is the 'IP' an IPv6 address?
+--
+--   >>> isIPv6 (ipv4 10 0 0 25)
+--   False
+--
+--   >>> isIPv6 (ipv6 0x3124 0x0 0x0 0xDEAD 0xCAFE 0xFF 0xFE00 0x1)
+--   True
+isIPv6 :: IP -> Bool
+isIPv6 = case_ (const False) (const True)
+{-# inline isIPv6 #-}
+
+-- | Print an 'IP' using the textual encoding. This exists mostly for
+--   debugging purposes.
+--
+--   >>> print (ipv4 10 0 0 25)
+--   10.0.0.25
+--
+--   >>> print (ipv6 0x3124 0x0 0x0 0xDEAD 0xCAFE 0xFF 0xFE00 0x1)
+--   3124::dead:cafe:ff:fe00:1
 print :: IP -> IO ()
 print = TIO.putStrLn . encode
 
diff --git a/src/Net/IPv4.hs b/src/Net/IPv4.hs
--- a/src/Net/IPv4.hs
+++ b/src/Net/IPv4.hs
@@ -122,6 +122,7 @@
 --
 -- These are here to get doctest's property checking to work
 --
+-- >>> :set -XOverloadedStrings
 -- >>> import Test.QuickCheck (Arbitrary(..))
 -- >>> import qualified Prelude as P
 -- >>> import qualified Data.Text.IO as T
@@ -262,10 +263,10 @@
 
 -- | Decode an 'IPv4' address.
 --
---   >>> decode (Text.pack "192.168.2.47")
+--   >>> decode "192.168.2.47"
 --   Just (ipv4 192 168 2 47)
 --
---   >>> decode (Text.pack "10.100.256.256")
+--   >>> decode "10.100.256.256"
 --   Nothing
 decode :: Text -> Maybe IPv4
 decode = decodeIPv4TextMaybe
@@ -279,20 +280,20 @@
 
 -- | Parse an 'IPv4' address using a 'TextRead.Reader'.
 --
---   >>> reader (Text.pack "192.168.2.47")
+--   >>> reader "192.168.2.47"
 --   Right (ipv4 192 168 2 47,"")
 --
---   >>> reader (Text.pack "192.168.2.470")
+--   >>> reader "192.168.2.470"
 --   Left "All octets in an IPv4 address must be between 0 and 255"
 reader :: TextRead.Reader IPv4
 reader = decodeIPv4TextReader
 
 -- | Parse an 'IPv4' address using a 'AT.Parser'.
 --
---   >>> AT.parseOnly parser (Text.pack "192.168.2.47")
+--   >>> AT.parseOnly parser "192.168.2.47"
 --   Right (ipv4 192 168 2 47)
 --
---   >>> AT.parseOnly parser (Text.pack "192.168.2.470")
+--   >>> AT.parseOnly parser "192.168.2.470"
 --   Left "Failed reading: All octets in an IPv4 address must be between 0 and 255"
 parser :: AT.Parser IPv4
 parser = dotDecimalParser
@@ -345,7 +346,7 @@
 
 -- | Decode a UTF8-encoded 'ByteString' into an 'IPv4'.
 --
---   >>> decodeUtf8 (BC8.pack "192.168.2.47")
+--   >>> decodeUtf8 "192.168.2.47"
 --   Just (ipv4 192 168 2 47)
 decodeUtf8 :: ByteString -> Maybe IPv4
 decodeUtf8 = decode <=< rightToMaybe . decodeUtf8'
@@ -358,10 +359,10 @@
 
 -- | Parse an 'IPv4' using a 'AB.Parser'.
 --
---   >>> AB.parseOnly parserUtf8 (BC8.pack "192.168.2.47")
+--   >>> AB.parseOnly parserUtf8 "192.168.2.47"
 --   Right (ipv4 192 168 2 47)
 --
---   >>> AB.parseOnly parserUtf8 (BC8.pack "192.168.2.470")
+--   >>> AB.parseOnly parserUtf8 "192.168.2.470"
 --   Left "Failed reading: All octets in an ipv4 address must be between 0 and 255"
 parserUtf8 :: AB.Parser IPv4
 parserUtf8 = fromOctets'
@@ -882,18 +883,32 @@
    in IPv4Range (IPv4 w') len'
 
 -- | Encode an 'IPv4Range' as 'Text'.
+--
+--   >>> encodeRange (IPv4Range (ipv4 172 16 0 0) 12)
+--   "172.16.0.0/12"
 encodeRange :: IPv4Range -> Text
 encodeRange = rangeToDotDecimalText
 
 -- | Decode an 'IPv4Range' from 'Text'.
+--
+--   >>> decodeRange "172.16.0.0/12"
+--   Just (IPv4Range {ipv4RangeBase = ipv4 172 16 0 0, ipv4RangeLength = 12})
+--   >>> decodeRange "192.168.25.254/16"
+--   Just (IPv4Range {ipv4RangeBase = ipv4 192 168 0 0, ipv4RangeLength = 16})
 decodeRange :: Text -> Maybe IPv4Range
 decodeRange = rightToMaybe . AT.parseOnly (parserRange <* AT.endOfInput)
 
 -- | Encode an 'IPv4Range' to a 'TBuilder.Builder'.
+--
+--   >>> builderRange (IPv4Range (ipv4 172 16 0 0) 12)
+--   "172.16.0.0/12"
 builderRange :: IPv4Range -> TBuilder.Builder
 builderRange = rangeToDotDecimalBuilder
 
--- | Parse an 'IPv4Range' using a 'AT.Parser.'
+-- | Parse an 'IPv4Range' using a 'AT.Parser'.
+--
+--   >>> AT.parseOnly parserRange "192.168.25.254/16"
+--   Right (IPv4Range {ipv4RangeBase = ipv4 192 168 0 0, ipv4RangeLength = 16})
 parserRange :: AT.Parser IPv4Range
 parserRange = do
   ip <- parser
@@ -906,7 +921,8 @@
       then fail "An IP range length must be between 0 and 32"
       else return i
 
--- | This exists mostly for testing purposes.
+-- | Print an 'IPv4Range'. Helper function that
+--   exists mostly for testing purposes.
 printRange :: IPv4Range -> IO ()
 printRange = TIO.putStrLn . encodeRange
 
