diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright Andrew Martin (c) 2017
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Andrew Martin nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,1 @@
+# quickcheck-classes
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/quickcheck-classes.cabal b/quickcheck-classes.cabal
new file mode 100644
--- /dev/null
+++ b/quickcheck-classes.cabal
@@ -0,0 +1,42 @@
+name: quickcheck-classes
+version: 0.1
+synopsis: QuickCheck common typeclasses
+description: QuickCheck common typeclasses
+homepage: https://github.com/andrewthad/quickcheck-classes#readme
+license: BSD3
+license-file: LICENSE
+author: Andrew Martin
+maintainer: andrew.thaddeus@gmail.com
+copyright: 2017 Andrew Martin
+category: software
+build-type: Simple
+extra-source-files: README.md
+cabal-version: >=1.10
+
+library
+  hs-source-dirs: src
+  exposed-modules:
+    Test.QuickCheck.Classes
+  build-depends:
+      base >= 4.7 && < 5
+    , QuickCheck
+    , primitive
+    , prim-array
+    , aeson
+  default-language: Haskell2010
+
+test-suite test
+  type: exitcode-stdio-1.0
+  hs-source-dirs: test
+  main-is: Spec.hs
+  build-depends:
+      base
+    , quickcheck-classes
+    , QuickCheck
+    , primitive
+    , aeson
+  default-language: Haskell2010
+
+source-repository head
+  type: git
+  location: https://github.com/andrewthad/quickcheck-classes
diff --git a/src/Test/QuickCheck/Classes.hs b/src/Test/QuickCheck/Classes.hs
new file mode 100644
--- /dev/null
+++ b/src/Test/QuickCheck/Classes.hs
@@ -0,0 +1,242 @@
+{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE MagicHash #-}
+{-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+module Test.QuickCheck.Classes
+  ( primProps
+  , storableProps
+  , semigroupProps
+  , monoidProps
+  , showReadProps
+  , jsonProps
+  ) where
+
+import Test.QuickCheck
+import Data.Primitive hiding (sizeOf,newArray,copyArray)
+import Data.Primitive.PrimArray
+import Data.Proxy
+import Control.Monad.ST
+import GHC.Ptr (Ptr(..))
+import Data.Primitive.Addr (Addr(..))
+import Foreign.Marshal.Alloc
+import System.IO.Unsafe
+import Data.Semigroup (Semigroup)
+import GHC.Exts (fromList,toList)
+import Foreign.Marshal.Array
+import Foreign.Storable
+import Text.Read (readMaybe)
+import Data.Aeson (FromJSON(..),ToJSON(..))
+import qualified Data.Aeson as AE
+import qualified Data.Primitive as P
+import qualified Data.Semigroup as SG
+import qualified GHC.OldList as L
+
+jsonProps :: (ToJSON a, FromJSON a, Show a, Arbitrary a, Eq a) => Proxy a -> [(String,Property)]
+jsonProps p =
+  [ ("Encoding Equals Value", jsonEncodingEqualsValue p)
+  , ("Partial Isomorphism", jsonEncodingPartialIsomorphism p)
+  ]
+
+showReadProps :: (Show a, Read a, Eq a, Arbitrary a) => Proxy a -> [(String,Property)]
+showReadProps p =
+  [ ("Partial Isomorphism", showReadPartialIsomorphism p)
+  ]
+
+semigroupProps :: (Semigroup a, Eq a, Arbitrary a, Show a) => Proxy a -> [(String,Property)]
+semigroupProps p =
+  [ ("Associative", semigroupAssociative p)
+  ]
+
+monoidProps :: (Monoid a, Eq a, Arbitrary a, Show a) => Proxy a -> [(String,Property)]
+monoidProps p =
+  [ ("Associative", monoidAssociative p)
+  , ("Left Identity", monoidLeftIdentity p)
+  , ("Right Identity", monoidRightIdentity p)
+  ]
+
+primProps :: (Prim a, Eq a, Arbitrary a, Show a) => Proxy a -> [(String,Property)]
+primProps p =
+  [ ("ByteArray Set-Get (you get back what you put in)", primSetGetByteArray p)
+  , ("ByteArray Get-Set (putting back what you got out has no effect)", primGetSetByteArray p)
+  , ("ByteArray Set-Set (setting twice is same as setting once)", primSetSetByteArray p)
+  , ("ByteArray List Conversion Roundtrips", primListByteArray p)
+  , ("Addr Set-Get (you get back what you put in)", primSetGetAddr p)
+  , ("Addr Get-Set (putting back what you got out has no effect)", primGetSetAddr p)
+  , ("Addr List Conversion Roundtrips", primListAddr p)
+  ]
+
+storableProps :: (Storable a, Eq a, Arbitrary a, Show a) => Proxy a -> [(String,Property)]
+storableProps p =
+  [ ("Set-Get (you get back what you put in)", storableSetGet p)
+  , ("Get-Set (putting back what you got out has no effect)", storableGetSet p)
+  , ("List Conversion Roundtrips", storableList p)
+  ]
+
+showReadPartialIsomorphism :: forall a. (Show a, Read a, Arbitrary a, Eq a) => Proxy a -> Property
+showReadPartialIsomorphism _ = property $ \(a :: a) ->
+  readMaybe (show a) == Just a
+
+-- TODO: improve the quality of the error message if
+-- something does not pass this test.
+jsonEncodingEqualsValue :: forall a. (ToJSON a, Show a, Arbitrary a) => Proxy a -> Property
+jsonEncodingEqualsValue _ = property $ \(a :: a) ->
+  case AE.decode (AE.encode a) of
+    Nothing -> False
+    Just (v :: AE.Value) -> v == toJSON a
+
+jsonEncodingPartialIsomorphism :: forall a. (ToJSON a, FromJSON a, Show a, Eq a, Arbitrary a) => Proxy a -> Property
+jsonEncodingPartialIsomorphism _ = property $ \(a :: a) ->
+  AE.decode (AE.encode a) == Just a
+
+semigroupAssociative :: forall a. (Semigroup a, Eq a, Arbitrary a, Show a) => Proxy a -> Property
+semigroupAssociative _ = property $ \(a :: a) b c -> a SG.<> (b SG.<> c) == (a SG.<> b) SG.<> c
+
+monoidAssociative :: forall a. (Monoid a, Eq a, Arbitrary a, Show a) => Proxy a -> Property
+monoidAssociative _ = property $ \(a :: a) b c -> mappend a (mappend b c) == mappend (mappend a b) c
+
+monoidLeftIdentity :: forall a. (Monoid a, Eq a, Arbitrary a, Show a) => Proxy a -> Property
+monoidLeftIdentity _ = property $ \(a :: a) -> mappend mempty a == a
+
+monoidRightIdentity :: forall a. (Monoid a, Eq a, Arbitrary a, Show a) => Proxy a -> Property
+monoidRightIdentity _ = property $ \(a :: a) -> mappend a mempty == a
+
+primListByteArray :: forall a. (Prim a, Eq a, Arbitrary a, Show a) => Proxy a -> Property
+primListByteArray _ = property $ \(as :: [a]) ->
+  as == toList (fromList as :: PrimArray a)
+
+primListAddr :: forall a. (Prim a, Eq a, Arbitrary a, Show a) => Proxy a -> Property
+primListAddr _ = property $ \(as :: [a]) -> unsafePerformIO $ do
+  let len = L.length as
+  ptr@(Ptr addr#) :: Ptr a <- mallocBytes (len * P.sizeOf (undefined :: a))
+  let addr = Addr addr#
+  let go :: Int -> [a] -> IO ()
+      go !ix xs = case xs of
+        [] -> return ()
+        (x : xsNext) -> do
+          writeOffAddr addr ix x
+          go (ix + 1) xsNext
+  go 0 as
+  let rebuild :: Int -> IO [a]
+      rebuild !ix = if ix < len
+        then (:) <$> readOffAddr addr ix <*> rebuild (ix + 1)
+        else return []
+  asNew <- rebuild 0
+  free ptr
+  return (as == asNew)
+
+primSetGetByteArray :: forall a. (Prim a, Eq a, Arbitrary a, Show a) => Proxy a -> Property
+primSetGetByteArray _ = property $ \(a :: a) len -> (len > 0) ==> do
+  ix <- choose (0,len - 1)
+  return $ runST $ do
+    arr <- newPrimArray len
+    writePrimArray arr ix a
+    a' <- readPrimArray arr ix
+    return (a == a')
+
+primGetSetByteArray :: forall a. (Prim a, Eq a, Arbitrary a, Show a) => Proxy a -> Property
+primGetSetByteArray _ = property $ \(as :: [a]) -> (not (L.null as)) ==> do
+  let arr1 = fromList as :: PrimArray a
+      len = L.length as
+  ix <- choose (0,len - 1)
+  arr2 <- return $ runST $ do
+    marr <- newPrimArray len
+    copyPrimArray marr 0 arr1 0 len
+    a <- readPrimArray marr ix
+    writePrimArray marr ix a
+    unsafeFreezePrimArray marr
+  return (arr1 == arr2)
+
+primSetSetByteArray :: forall a. (Prim a, Eq a, Arbitrary a, Show a) => Proxy a -> Property
+primSetSetByteArray _ = property $ \(a :: a) (as :: [a]) -> (not (L.null as)) ==> do
+  let arr1 = fromList as :: PrimArray a
+      len = L.length as
+  ix <- choose (0,len - 1)
+  (arr2,arr3) <- return $ runST $ do
+    marr2 <- newPrimArray len
+    copyPrimArray marr2 0 arr1 0 len
+    writePrimArray marr2 ix a
+    marr3 <- newPrimArray len
+    copyMutablePrimArray marr3 0 marr2 0 len
+    arr2 <- unsafeFreezePrimArray marr2
+    writePrimArray marr3 ix a
+    arr3 <- unsafeFreezePrimArray marr3
+    return (arr2,arr3)
+  return (arr2 == arr3)
+
+primSetGetAddr :: forall a. (Prim a, Eq a, Arbitrary a, Show a) => Proxy a -> Property
+primSetGetAddr _ = property $ \(a :: a) len -> (len > 0) ==> do
+  ix <- choose (0,len - 1)
+  return $ unsafePerformIO $ do
+    ptr@(Ptr addr#) :: Ptr a <- mallocBytes (len * P.sizeOf (undefined :: a))
+    let addr = Addr addr#
+    writeOffAddr addr ix a
+    a' <- readOffAddr addr ix
+    free ptr
+    return (a == a')
+
+primGetSetAddr :: forall a. (Prim a, Eq a, Arbitrary a, Show a) => Proxy a -> Property
+primGetSetAddr _ = property $ \(as :: [a]) -> (not (L.null as)) ==> do
+  let arr1 = fromList as :: PrimArray a
+      len = L.length as
+  ix <- choose (0,len - 1)
+  arr2 <- return $ unsafePerformIO $ do
+    ptr@(Ptr addr#) :: Ptr a <- mallocBytes (len * P.sizeOf (undefined :: a))
+    let addr = Addr addr#
+    copyPrimArrayToPtr ptr arr1 0 len
+    a :: a <- readOffAddr addr ix
+    writeOffAddr addr ix a
+    marr <- newPrimArray len
+    copyPtrToMutablePrimArray marr 0 ptr len
+    free ptr
+    unsafeFreezePrimArray marr
+  return (arr1 == arr2)
+
+storableSetGet :: forall a. (Storable a, Eq a, Arbitrary a, Show a) => Proxy a -> Property
+storableSetGet _ = property $ \(a :: a) len -> (len > 0) ==> do
+  ix <- choose (0,len - 1)
+  return $ unsafePerformIO $ do
+    ptr :: Ptr a <- mallocArray len
+    pokeElemOff ptr ix a
+    a' <- peekElemOff ptr ix
+    free ptr
+    return (a == a')
+
+storableGetSet :: forall a. (Storable a, Eq a, Arbitrary a, Show a) => Proxy a -> Property
+storableGetSet _ = property $ \(as :: [a]) -> (not (L.null as)) ==> do
+  let len = L.length as
+  ix <- choose (0,len - 1)
+  return $ unsafePerformIO $ do
+    ptrA <- newArray as
+    ptrB <- mallocArray len
+    copyArray ptrB ptrA len
+    a <- peekElemOff ptrA ix
+    pokeElemOff ptrA ix a
+    res <- arrayEq ptrA ptrB len
+    free ptrA
+    free ptrB
+    return res
+
+storableList :: forall a. (Storable a, Eq a, Arbitrary a, Show a) => Proxy a -> Property
+storableList _ = property $ \(as :: [a]) -> unsafePerformIO $ do
+  let len = L.length as
+  ptr <- newArray as
+  let rebuild :: Int -> IO [a]
+      rebuild !ix = if ix < len
+        then (:) <$> peekElemOff ptr ix <*> rebuild (ix + 1)
+        else return []
+  asNew <- rebuild 0
+  free ptr
+  return (as == asNew)
+
+arrayEq :: forall a. (Storable a, Eq a) => Ptr a -> Ptr a -> Int -> IO Bool
+arrayEq ptrA ptrB len = go 0 where
+  go !i = if i < len
+    then do
+      a <- peekElemOff ptrA i
+      b <- peekElemOff ptrB i
+      if a == b
+        then go (i + 1)
+        else return False
+    else return True
+
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec.hs
@@ -0,0 +1,62 @@
+{-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+import Test.QuickCheck
+import Data.Proxy
+import Data.Word
+import Data.Int
+import Control.Monad
+import Data.Primitive
+import Data.Foldable
+import Data.Monoid (Sum)
+import Foreign.Storable
+import Data.Aeson (ToJSON,FromJSON)
+
+import Test.QuickCheck.Classes
+
+main :: IO ()
+main = do
+  putStrLn "Testing properties for common typeclasses"
+  r <- flip foldlMapM allPropsApplied $ \(typeName,properties) -> do
+    putStrLn $ "------------"
+    putStrLn $ "-- " ++ typeName
+    putStrLn $ "------------"
+    flip foldlMapM properties $ \(name,p) -> do
+      putStrLn name
+      r <- quickCheckResult p
+      return $ case r of
+        Success _ _ _ -> Good
+        _ -> Bad
+  putStrLn ""
+  case r of
+    Good -> putStrLn "All tests succeeded"
+    Bad -> putStrLn "One or more tests failed"
+
+data Status = Bad | Good
+
+instance Monoid Status where
+  mempty = Good
+  mappend Good x = x
+  mappend Bad _ = Bad
+
+allPropsApplied :: [(String,[(String,Property)])]
+allPropsApplied = 
+  [ ("Word8",allProps (Proxy :: Proxy Word8))
+  , ("Int16",allProps (Proxy :: Proxy Int16))
+  , ("Int",allProps (Proxy :: Proxy Int))
+  , ("Word",allProps (Proxy :: Proxy Word))
+  , ("Int64",allProps (Proxy :: Proxy Int64))
+  ]
+
+allProps :: forall a. (Num a, Prim a, Storable a, Eq a, Arbitrary a, Show a, Read a, ToJSON a, FromJSON a) => Proxy a -> [(String,Property)]
+allProps p = concat
+  [ primProps p
+  , storableProps p
+  , monoidProps (Proxy :: Proxy (Sum a))
+  , showReadProps p
+  , jsonProps p
+  ]
+
+foldlMapM :: (Foldable t, Monoid b, Monad m) => (a -> m b) -> t a -> m b
+foldlMapM f = foldlM (\b a -> fmap (mappend b) (f a)) mempty
+
