diff --git a/ChangeLog.md b/ChangeLog.md
new file mode 100644
--- /dev/null
+++ b/ChangeLog.md
@@ -0,0 +1,3 @@
+# Changelog for abides
+
+## Unreleased changes
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright Athan Clark (c) 2019
+
+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 Author name here 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,7 @@
+# abides
+
+
+![](https://github.com/athanclark/abides/raw/master/images/the_dude.jpg)
+
+
+Just a bunch of functions that make sure a class abides by its laws, man.
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/abides.cabal b/abides.cabal
new file mode 100644
--- /dev/null
+++ b/abides.cabal
@@ -0,0 +1,70 @@
+cabal-version: 1.12
+
+-- This file has been generated from package.yaml by hpack version 0.31.2.
+--
+-- see: https://github.com/sol/hpack
+--
+-- hash: f1fefe867335372cccdb4d04ab5a2e0285abcc0e8080fbce2e12adf70dc571bd
+
+name:           abides
+version:        0.0.0
+synopsis:       Simple boolean tests to see if a value abides by certain properties
+description:    Please see the README on GitHub at <https://github.com/athanclark/abides#readme>
+category:       Data, Testing
+homepage:       https://github.com/athanclark/abides#readme
+bug-reports:    https://github.com/athanclark/abides/issues
+author:         Athan Clark
+maintainer:     athan.clark@gmail.com
+copyright:      2019 Athan Clark
+license:        BSD3
+license-file:   LICENSE
+build-type:     Simple
+extra-source-files:
+    README.md
+    ChangeLog.md
+
+source-repository head
+  type: git
+  location: https://github.com/athanclark/abides
+
+library
+  exposed-modules:
+      Test.Abides
+      Test.Abides.Control.Alternative
+      Test.Abides.Control.Applicative
+      Test.Abides.Control.Category
+      Test.Abides.Control.Comonad
+      Test.Abides.Control.Monad
+      Test.Abides.Data.Bounded
+      Test.Abides.Data.Enum
+      Test.Abides.Data.Eq
+      Test.Abides.Data.Foldable
+      Test.Abides.Data.Functor
+      Test.Abides.Data.Monoid
+      Test.Abides.Data.Ord
+      Test.Abides.Data.Semigroup
+      Test.Abides.Properties
+  other-modules:
+      Paths_abides
+  hs-source-dirs:
+      src
+  build-depends:
+      base >=4.7 && <5
+    , comonad
+  default-language: Haskell2010
+
+test-suite abides-test
+  type: exitcode-stdio-1.0
+  main-is: Spec.hs
+  other-modules:
+      Paths_abides
+  hs-source-dirs:
+      test
+  ghc-options: -threaded -rtsopts -with-rtsopts=-N
+  build-depends:
+      abides
+    , base >=4.7 && <5
+    , comonad
+    , tasty
+    , tasty-quickcheck
+  default-language: Haskell2010
diff --git a/src/Test/Abides.hs b/src/Test/Abides.hs
new file mode 100644
--- /dev/null
+++ b/src/Test/Abides.hs
@@ -0,0 +1,2 @@
+module Test.Abides where
+
diff --git a/src/Test/Abides/Control/Alternative.hs b/src/Test/Abides/Control/Alternative.hs
new file mode 100644
--- /dev/null
+++ b/src/Test/Abides/Control/Alternative.hs
@@ -0,0 +1,13 @@
+module Test.Abides.Control.Alternative where
+
+import Control.Applicative (Alternative ((<|>)), empty)
+
+
+-- | (f <|> g) <*> x == (f <*> x) <|> (g <|> x)
+distributive :: Alternative f => Applicative f => Eq (f b) => f (a -> b) -> f (a -> b) -> f a -> Bool
+distributive f g x = ((f <|> g) <*> x) == ((f <*> x) <|> (g <*> x))
+
+
+-- | empty <*> x == empty
+annihilation :: Alternative f => Applicative f => Eq (f b) => f (a -> b) -> Bool
+annihilation f = (f <*> empty) == empty
diff --git a/src/Test/Abides/Control/Applicative.hs b/src/Test/Abides/Control/Applicative.hs
new file mode 100644
--- /dev/null
+++ b/src/Test/Abides/Control/Applicative.hs
@@ -0,0 +1,28 @@
+{-# LANGUAGE
+    RankNTypes
+  , ScopedTypeVariables
+  #-}
+
+module Test.Abides.Control.Applicative where
+
+
+identity :: Applicative f => Eq (f a) => f a -> Bool
+identity x = (pure id <*> x) == x
+
+
+composition :: Applicative f => Eq (f c) => f (b -> c) -> f (a -> b) -> f a -> Bool
+composition f g h = (pure (.) <*> f <*> g <*> h) == (f <*> (g <*> h))
+
+
+-- FIXME ambiguous type?
+-- homomorphism :: forall f a b. Applicative f => Eq (f b) => (a -> b) -> a -> Bool
+-- homomorphism f x = (f' <*> x') == y'
+--   where
+--     f' :: f (a -> b)
+--     f' = pure f
+
+--     x' :: f a
+--     x' = pure x
+
+--     y' :: f b
+--     y' = pure (f x)
diff --git a/src/Test/Abides/Control/Category.hs b/src/Test/Abides/Control/Category.hs
new file mode 100644
--- /dev/null
+++ b/src/Test/Abides/Control/Category.hs
@@ -0,0 +1,14 @@
+{-# LANGUAGE NoImplicitPrelude #-}
+
+module Test.Abides.Control.Category where
+
+import Prelude hiding ((.), id)
+import Control.Category (Category ((.), id))
+
+
+identity :: Category c => Eq (c a b) => c a b -> Bool
+identity p = ((id . p) == p) && ((p . id) == p)
+
+
+associative :: Category q => Eq (q a d) => q c d -> q b c -> q a b -> Bool
+associative x y z = (x . (y . z)) == ((x . y) . z)
diff --git a/src/Test/Abides/Control/Comonad.hs b/src/Test/Abides/Control/Comonad.hs
new file mode 100644
--- /dev/null
+++ b/src/Test/Abides/Control/Comonad.hs
@@ -0,0 +1,15 @@
+module Test.Abides.Control.Comonad where
+
+import Control.Comonad (Comonad (extend, extract), (<<=))
+
+
+leftIdentity :: Comonad w => Eq (w a) => w a -> Bool
+leftIdentity x = (extract <<= x) == x
+
+
+rightIdentity :: Comonad w => Eq a => (w a -> a) -> w a -> Bool
+rightIdentity f x = extract (f <<= x) == f x
+
+
+associative :: Comonad w => Eq (w c) => (w b -> c) -> (w a -> b) -> w a -> Bool
+associative f g x = extend f (extend g x) == extend (f . extend g) x
diff --git a/src/Test/Abides/Control/Monad.hs b/src/Test/Abides/Control/Monad.hs
new file mode 100644
--- /dev/null
+++ b/src/Test/Abides/Control/Monad.hs
@@ -0,0 +1,24 @@
+module Test.Abides.Control.Monad where
+
+
+import Control.Monad (MonadPlus (mzero, mplus))
+
+
+leftIdentity :: Monad m => Eq (m b) => (a -> m b) -> a -> Bool
+leftIdentity f x = (pure x >>= f) == f x
+
+
+rightIdentity :: Monad m => Eq (m a) => m a -> Bool
+rightIdentity x = (x >>= pure) == x
+
+
+associative :: Monad m => Eq (m c) => (a -> m b) -> (b -> m c) -> m a -> Bool
+associative f g x = ((x >>= f) >>= g) == (x >>= (\k -> f k >>= g))
+
+
+annihilation :: MonadPlus m => Eq (m b) => (a -> m b) -> Bool
+annihilation f = (mzero >>= f) == mzero
+
+
+distributive :: MonadPlus m => Eq (m b) => (a -> m b) -> m a -> m a -> Bool
+distributive f x y = (mplus x y >>= f) == mplus (x >>= f) (y >>= f)
diff --git a/src/Test/Abides/Data/Bounded.hs b/src/Test/Abides/Data/Bounded.hs
new file mode 100644
--- /dev/null
+++ b/src/Test/Abides/Data/Bounded.hs
@@ -0,0 +1,5 @@
+module Test.Abides.Data.Bounded where
+
+-- | minBound <= x <= maxBound?
+bounded :: Bounded a => Ord a => a -> Bool
+bounded x = minBound <= x && x <= maxBound
diff --git a/src/Test/Abides/Data/Enum.hs b/src/Test/Abides/Data/Enum.hs
new file mode 100644
--- /dev/null
+++ b/src/Test/Abides/Data/Enum.hs
@@ -0,0 +1,16 @@
+module Test.Abides.Data.Enum where
+
+
+-- | compare x y == compare (fromEnum x) (fromEnum y)
+compareHom :: Enum a => Ord a => a -> a -> Bool
+compareHom x y = compare x y == compare (fromEnum x) (fromEnum y)
+
+
+-- | pred (succ x) == x
+predsucc :: Enum a => Eq a => a -> Bool
+predsucc x = pred (succ x) == x
+
+
+-- | succ (pred x) == x
+succpred :: Enum a => Eq a => a -> Bool
+succpred x = succ (pred x) == x
diff --git a/src/Test/Abides/Data/Eq.hs b/src/Test/Abides/Data/Eq.hs
new file mode 100644
--- /dev/null
+++ b/src/Test/Abides/Data/Eq.hs
@@ -0,0 +1,23 @@
+module Test.Abides.Data.Eq
+  ( P.reflexive
+  , symmetry
+  , transitive
+  , negation
+  ) where
+
+import qualified Test.Abides.Properties as P
+
+
+-- | x == y => y == x?
+symmetry :: Eq a => a -> a -> Bool
+symmetry x y = if x == y then y == x else True
+
+
+-- | x == y && y == z => x == z
+transitive :: Eq a => a -> a -> a -> Bool
+transitive x y z = if x == y && y == z then x == z else True
+
+
+-- | x /= y => not (x == y)
+negation :: Eq a => a -> a -> Bool
+negation x y = if x /= y then not (x == y) else True
diff --git a/src/Test/Abides/Data/Foldable.hs b/src/Test/Abides/Data/Foldable.hs
new file mode 100644
--- /dev/null
+++ b/src/Test/Abides/Data/Foldable.hs
@@ -0,0 +1,8 @@
+module Test.Abides.Data.Foldable where
+
+import Data.Foldable (fold)
+
+
+-- | fold (map f x) == foldMap f x
+foldMap' :: Foldable f => Functor f => Monoid b => Eq b => (a -> b) -> f a -> Bool
+foldMap' f x = fold (fmap f x) == foldMap f x
diff --git a/src/Test/Abides/Data/Functor.hs b/src/Test/Abides/Data/Functor.hs
new file mode 100644
--- /dev/null
+++ b/src/Test/Abides/Data/Functor.hs
@@ -0,0 +1,10 @@
+module Test.Abides.Data.Functor where
+
+
+-- | fmap id x == x?
+identity :: Functor f => Eq (f a) => f a -> Bool
+identity x = fmap id x == x
+
+-- | fmap (f . g) x == fmap f (fmap g x)?
+composition :: Functor f => Eq (f a) => (a -> a) -> (a -> a) -> f a -> Bool
+composition f g x = fmap (f . g) x == fmap f (fmap g x)
diff --git a/src/Test/Abides/Data/Monoid.hs b/src/Test/Abides/Data/Monoid.hs
new file mode 100644
--- /dev/null
+++ b/src/Test/Abides/Data/Monoid.hs
@@ -0,0 +1,11 @@
+module Test.Abides.Data.Monoid where
+
+import Test.Abides.Properties (constL, constR)
+
+
+leftIdentity :: Monoid a => Eq a => a -> Bool
+leftIdentity = constR (<>) mempty
+
+
+rightIdentity :: Monoid a => Eq a => a -> Bool
+rightIdentity x = constL (<>) x mempty
diff --git a/src/Test/Abides/Data/Ord.hs b/src/Test/Abides/Data/Ord.hs
new file mode 100644
--- /dev/null
+++ b/src/Test/Abides/Data/Ord.hs
@@ -0,0 +1,16 @@
+module Test.Abides.Data.Ord where
+
+
+-- | x <= x?
+reflexive :: Ord a => a -> Bool
+reflexive x = x <= x
+
+
+-- | x <= y && y <= x => x == y
+antisymmetry :: Ord a => a -> a -> Bool
+antisymmetry x y = if x <= y && y <= x then x == y else True
+
+
+-- | x <= y && y <= z => x <= z
+transitive :: Ord a => a -> a -> a -> Bool
+transitive x y z = if x <= y && y <= z then x <= z else True
diff --git a/src/Test/Abides/Data/Semigroup.hs b/src/Test/Abides/Data/Semigroup.hs
new file mode 100644
--- /dev/null
+++ b/src/Test/Abides/Data/Semigroup.hs
@@ -0,0 +1,7 @@
+module Test.Abides.Data.Semigroup where
+
+import qualified Test.Abides.Properties as P
+
+
+associative :: Semigroup a => Eq a => a -> a -> a -> Bool
+associative = P.associative (<>)
diff --git a/src/Test/Abides/Properties.hs b/src/Test/Abides/Properties.hs
new file mode 100644
--- /dev/null
+++ b/src/Test/Abides/Properties.hs
@@ -0,0 +1,31 @@
+module Test.Abides.Properties where
+
+
+-- | x == x?
+reflexive :: Eq a => a -> Bool
+reflexive x = x == x
+
+-- | f x y == f y x?
+commutative :: Eq b => (a -> a -> b) -> a -> a -> Bool
+commutative f x y = f x y == f y x
+
+-- | f (f x y) z == f x (f y z)?
+associative :: Eq a => (a -> a -> a) -> a -> a -> a -> Bool
+associative f x y z = f (f x y) z == f x (f y z)
+
+-- | f (f x) == f x?
+idempotent :: Eq a => (a -> a) -> a -> Bool
+idempotent f x = f (f x) == f x
+
+-- | f (g x y) == g (f x) (f y)?
+distributive :: Eq a => (a -> a) -> (a -> a -> a) -> a -> a -> Bool
+distributive f g x y = f (g x y) == g (f x) (f y)
+
+-- | f x y == x? Note: bottom ~ forall y. f bottom y == bottom, while unit ~ forall x. f x unit == x
+constL :: Eq a => (a -> a -> a) -> a -> a -> Bool
+constL f x y = f x y == x
+
+-- | f x y == y?
+constR :: Eq a => (a -> a -> a) -> a -> a -> Bool
+constR f x y = f x y == y
+
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec.hs
@@ -0,0 +1,109 @@
+import qualified Test.Abides.Control.Alternative as Alt
+import qualified Test.Abides.Control.Applicative as App
+import qualified Test.Abides.Control.Category as Cat
+import qualified Test.Abides.Control.Comonad as Com
+import qualified Test.Abides.Control.Monad as Mon
+import qualified Test.Abides.Data.Bounded as Bou
+import qualified Test.Abides.Data.Enum as Enu
+import qualified Test.Abides.Data.Eq as Eq
+import qualified Test.Abides.Data.Foldable as Fol
+import qualified Test.Abides.Data.Functor as Fun
+import qualified Test.Abides.Data.Monoid as Mono
+import qualified Test.Abides.Data.Ord as Ord
+import qualified Test.Abides.Data.Semigroup as Sem
+
+import Test.Tasty (defaultMain, testGroup)
+import Test.Tasty.QuickCheck (testProperty)
+
+import Data.Monoid (Sum (..))
+
+
+
+main :: IO ()
+main = defaultMain $ testGroup "All Tests"
+  [ testGroup "Test.Abides.Control"
+    [ testProperty "Alternative" alternativeTests
+    , testProperty "Applicative" applicativeTests
+    -- , testProperty "Category" categoryTests
+    , testProperty "Comonad" comonadTests
+    , testProperty "Monad" monadTests
+    , testProperty "MonadPlus" monadPlusTests
+    ]
+  , testGroup "Test.Abides.Data"
+    [ testProperty "Bounded" boundedTests
+    , testProperty "Enum" enumTests
+    , testProperty "Eq" eqTests
+    , testProperty "Foldable" foldableTests
+    , testProperty "Functor" functorTests
+    , testProperty "Monoid" monoidTests
+    , testProperty "Ord" ordTests
+    , testProperty "Semigroup" semigroupTests
+    ]
+  ]
+  where
+    alternativeTests :: [Int] -> Bool
+    alternativeTests x = and
+      [ Alt.distributive [(+ 1)] [(* 2)] x
+      , Alt.annihilation (map (+) x)
+      ]
+    applicativeTests :: [Int] -> Bool
+    applicativeTests x = and
+      [ App.identity x
+      , App.composition [(+ 1)] [(* 2)] x
+      ]
+    -- categoryTests :: Kleisli [] Int Int -> Bool
+    -- categoryTests x = and
+    --   [ Cat.identity x
+    --   , App.composition [(+ 1)] [(* 2)] x
+    --   ]
+    comonadTests :: (Int,Int) -> Bool
+    comonadTests x = and
+      [ Com.leftIdentity x
+      , Com.rightIdentity (\(a,b) -> b + 1) x
+      , Com.associative (\(a,b) -> b * 2) (\(a,b) -> b + 1) x
+      ]
+    monadTests :: [Int] -> Bool
+    monadTests x = and
+      [ Mon.leftIdentity (\a -> [a] ++ x) 5
+      , Mon.rightIdentity x
+      , Mon.associative (\a -> [a,1]) (\a -> [2,a]) x
+      ]
+    monadPlusTests :: Maybe Int -> Bool
+    monadPlusTests x = and
+      [ Mon.annihilation (\a -> Just (a + 1))
+      , Mon.distributive (\a -> Just (a + 1)) x (Just 5)
+      ]
+    boundedTests :: Int -> Bool
+    boundedTests x = Bou.bounded x
+    enumTests :: Int -> Bool
+    enumTests x = and
+      [ Enu.compareHom x 1
+      , Enu.predsucc x
+      , Enu.succpred x
+      ]
+    eqTests :: (Int,Int) -> Bool
+    eqTests (x,y) = and
+      [ Eq.symmetry x y
+      , Eq.transitive x y x
+      , Eq.negation x y
+      ]
+    foldableTests :: [Sum Int] -> Bool
+    foldableTests x = Fol.foldMap' (+ (Sum 5)) x
+    functorTests :: [Int] -> Bool
+    functorTests x = and
+      [ Fun.identity x
+      , Fun.composition (+ 5) (* 2) x
+      ]
+    monoidTests :: Sum Int -> Bool
+    monoidTests x = and
+      [ Mono.leftIdentity x
+      , Mono.rightIdentity x
+      ]
+    ordTests :: (Int,Int) -> Bool
+    ordTests (x,y) = and
+      [ Ord.reflexive x
+      , Ord.antisymmetry x y
+      , Ord.transitive x y x
+      ]
+    semigroupTests :: (Sum Int, Sum Int, Sum Int) -> Bool
+    semigroupTests (x,y,z) = Sem.associative x y z
