diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2017, Global Access Internet Services GmbH
+
+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 Pavlo Kerestey 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,54 @@
+# Setoid
+
+A Haskell implementation of [setoid](https://en.wikipedia.org/wiki/Setoid) - a
+set equipped with an equivalence relation. Setoid is a useful data structure in
+cases where equivalence is chosen not to be equality. One can use it to
+influence the memberships of the elements more strictly, as opposed to sets, and
+run computations on unions when conflicts between elements are found.
+
+For more in-depth explanations and examples, please have a look at the
+documentation on hackage.
+
+### Copyright:
+* [Global Access Internet Services GmbH](http://www.global.de)
+
+### Authors:
+* [Pavlo Kerestey](https://github.com/ptek)
+* [Simon Zelazny](https://github.com/pzel)
+* [Irek Jozwiak](https://github.com/irekjozwiak) - Author of the predecessor
+  implementation of sets wich stricter guarantees and the idea of Setoid in
+  Haskell.
+
+## License
+
+```text
+    Copyright (c) 2017, Global Access Internet Services GmbH
+    
+    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 Global Access Internet Services GmbH 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/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/changelog.md b/changelog.md
new file mode 100644
--- /dev/null
+++ b/changelog.md
@@ -0,0 +1,5 @@
+# Revision history for setoid
+
+## 0.1.0.0  -- 2017-01-17
+
+* Initial extract of Setoid into a standalone library
diff --git a/setoid.cabal b/setoid.cabal
new file mode 100644
--- /dev/null
+++ b/setoid.cabal
@@ -0,0 +1,55 @@
+-- This file has been generated from package.yaml by hpack version 0.15.0.
+--
+-- see: https://github.com/sol/hpack
+
+name:                setoid
+version:             0.1.0.0
+synopsis:            A Haskell implementation of setoid
+description:         Setoid (<https://en.wikipedia.org/wiki/Setoid>) is a set equipped with an equivalence relation. Setoid is a useful data structure in cases where equivalence is chosen not to be equality. One can use it to influence the memberships of the elements more strictly, as opposed to sets, and run computations on unions when conflicts between elements are found.
+                     You can find more deatails in `Data.Setoid`
+license:             BSD3
+license-file:        LICENSE
+author:              Pavlo Kerestey
+maintainer:          kerestey@global.de
+copyright:           Global Access Internet Services GmbH
+category:            Data
+build-type:          Simple
+cabal-version:       >= 1.10
+
+extra-source-files:
+    changelog.md
+    README.md
+
+library
+  hs-source-dirs:
+      src
+  ghc-options: -Wall
+  default-language: Haskell2010
+  exposed-modules:
+      Data.Setoid
+      Data.Setoid.Equivalence
+      Data.Setoid.Types
+  build-depends:
+      base >= 4.7 && < 5
+    , containers >= 0.5 && < 0.6
+
+test-suite setoid-test
+  type: exitcode-stdio-1.0
+  ghc-options: -Wall -threaded  -rtsopts -with-rtsopts=-N -O
+  main-is: RunTests.hs
+  other-modules:
+      Data.SetoidTest
+  default-language: Haskell2010
+  hs-source-dirs:
+      tests
+  build-depends:
+      base >= 4.7 && < 5
+    , containers >= 0.5 && < 0.6
+    , mtl
+    , setoid
+    , smallcheck
+    , tasty
+    , tasty-discover
+    , tasty-hunit
+    , tasty-quickcheck
+    , tasty-smallcheck
diff --git a/src/Data/Setoid.hs b/src/Data/Setoid.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Setoid.hs
@@ -0,0 +1,344 @@
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# OPTIONS_GHC -fno-warn-orphans #-}
+
+--------------------------------------------------------------------
+{- |
+Module    : Data.Setoid
+Copyright : (c) Global Access Internet Services GmbH
+License   : BSD3
+Maintainer: Pavlo Kerestey <pavlo@kerestey.net>
+
+A Haskell implementation of
+<https://en.wikipedia.org/wiki/Setoid setoid> - a set equipped with
+an equivalence relation. Setoid is a useful data structure when
+equivalence is chosen not to be equality. This allows to influence the
+membership of the elements in a setoid. When equality is all one needs
+- using sets is a better option.
+
+Here we have chosen to use a specific variant of equivalence of
+transforming the elements to comparable intermediaries. Although it
+does not make every equivalence relation possible, it is a practial
+choice for a lot of computations.
+
+== Usage
+
+When manipulating collections of objects in the real world, we often
+use lists/arrays. Sometimes we need to represent some properties of
+the relation between the elements though, and the lists do not provide
+such possibility. This library not only provides the guarantee that a
+setoid is correct by construction, but also that the manipulations
+will not change its structure.
+
+We use it to run computations over time series of sampling data,
+collections of users (who are unique by username or email) - to keep
+the same structure as the one which would be used in the database with
+unique indexes.
+
+To implement equivalence we chose to use a data class `EquivalenceBy`
+which provides a method of mapping an element to an intermediary,
+which is then used for comparison and ultimately lead to a choice
+of the members.
+
+The type of a setoid is `Setoid e a` where `a` is the member type and
+e is the type of equivalence intermediary. To chose the members of the
+setoid we compare the e(quivalences) of the elements with each other.
+
+The definition of `EquivalenceBy e a` is
+
+@
+class EquivalenceBy e a where
+  eqRel :: a -> e
+@
+
+To give a simple example of how the library could be used we will
+combine apples and oranges to a Setoid of fruit names by color. We
+want one fruit per colour as a result and don't care if its apple or
+an orange.
+
+@
+import Data.Setoid (Setoid)
+import qualified Data.Setoid as Setoid
+
+data Colour = Red | Green | Blue deriving (Eq,Ord)
+
+instance EquivalenceBy Colour (Colour,String) where
+  eqRel = fst
+
+apples, organges, fruits :: Setoid Int (Int,String)
+apples  = Setoid.fromList [(Green,"golden delicious"), (Orange,"honeycrunch")]
+oranges = Setoid.fromList [(Orange,"seville"), (Red,"blood orange")]
+
+fruits = apples `Setoid.union` oranges
+-- > [(Green,"golden delicious"), (Orange,"seville"), (Red,"blood orange")]
+@
+
+One can see the benefit of using a `Setoid` instead of "Data.List"
+because with the latter, we would have to use 'Data.List.nubBy' every
+time the data is transformed.
+
+When performing a `union`, our implementation would use `max` between
+two equivalent elements to resolve the conflict. Bear in mind, that
+the elements, though equivalent, might not be equal. In the example
+above, ordering of @ "seville" @ is bigger than @ "golden delicious" @
+thus @ ("Orange", "seville") @ is chosen in the result.
+
+=== Friends of friends and computation on union
+
+For another example, lets get all the users of two different services
+F and G. We are not interested in the different details, but want the
+instance of the users to be unique.
+
+@
+type Email = String
+data User = User {
+  email :: Email,
+  contacts :: Int
+  } deriving (Eq,Show)
+
+instance EquivalenceBy Email User where
+eqRel u = email u
+
+usersF, usersG, allUsers :: Setoid Email User
+usersF <- getUsers F
+usersG <- getUsers G
+
+allUsers = Setoid.unionWith mergeContactDetails usersF usersG
+
+mergeContactDetails :: User -> User -> User
+mergeContactDetails a b = User (email a) (contacts a + contacts b)
+-- ... --
+@
+
+We assume that here are equivalent elements in both setoids - in this
+case they have the same email adress. Thus we use `unionWith` to merge
+the other details of the contact. Here, we could also do computations
+and, for example, sum the number of friends/contacts from bothe
+services.
+
+Here is also one of the shortcommings of the
+library. mergeContactDetails choses the email of the first
+argument. Sinse in the context of unionWith, the emails of the first
+and the second users are the same. It is not nice from the perspective
+of the function itself though.
+
+@ Setoid.size allUsers @ Would give us the amount of all unique users
+in both services together.
+
+== Future Work
+
+- There is an unproven hypothesis about a relation between setoids and
+  Quotient Sets. It seems, that a `Setoid (a,b) (a,b,c)` is equivalent
+  to a `QuotientSet a (Setoid b (a,b,c))`. This means that every
+  QuotientSet can actually be represented as a setoid.
+
+- Performance is another issue. Current implementation uses the
+  `newtype Setoid x y = Setoid (Map x y)` which may be inefficient.
+
+
+-}
+--------------------------------------------------------------------
+module Data.Setoid
+  ( -- * Type
+    Setoid
+    -- * Class
+  , EquivalenceBy(..)
+    -- * Operators
+  , (=~=)
+  , (\\)
+  , (∪)
+    -- * Construction
+  , empty
+  , ø
+  , singleton
+  , union
+  , unions
+  , unionWith
+    -- * Difference
+  , difference
+    -- * Filter
+  , filter
+    -- * Query
+  , null
+  , size
+  , member
+  , equivalence
+    -- * Traversal
+    -- ** map
+  , map
+  , mapResolve
+  , mapM
+    -- * Conversion
+  , fromList
+  , fromListWith
+  , toList
+  ) where
+
+import qualified Data.List               as List
+import qualified Data.Map.Strict         as Map
+import           Data.Setoid.Equivalence
+import           Data.Setoid.Types
+import           Prelude                 hiding (filter, lookup, map, mapM,
+                                          mapM_, null, zip)
+import qualified Prelude                 as P
+
+-- | Instance Show, used for debugging
+instance (Show a) =>
+         Show (Setoid e a) where
+  show s = "{{ " ++ P.unlines (P.map show (toList s)) ++ " }}"
+
+-- | Monoid instance for Setoid
+instance (Ord e, Ord a) =>
+         Monoid (Setoid e a) where
+  mempty = empty
+  mappend = union
+  mconcat = unions
+
+-- * Operators
+infix 4 =~=
+
+-- | Same as `equivalence`
+(=~=)
+  :: (Eq e)
+  => Setoid e a -> Setoid e a -> Bool
+(=~=) = equivalence
+
+infix 5 \\
+
+-- | Same as `difference`
+(\\)
+  :: (Ord e)
+  => Setoid e a -> Setoid e a -> Setoid e a
+(\\) = difference
+
+-- | Same as `union`
+(∪)
+  :: (Ord e, Ord a)
+  => Setoid e a -> Setoid e a -> Setoid e a
+(∪) = union
+
+-- * Construction
+-- | An empty Setoid
+empty :: Setoid e a
+empty = Setoid Map.empty
+
+-- | Same as `empty`
+ø :: Setoid e a
+ø = empty
+
+-- | A Setoid with a single element
+singleton
+  :: (EquivalenceBy e a)
+  => a -> Setoid e a
+singleton a = Setoid (Map.singleton (eqRel a) a)
+
+-- ** Combining
+-- | Combine two Setoids resolving conflicts with `max` by
+-- default. This makes the union operation commutative and
+-- associative.
+union
+  :: (Ord e, Ord a)
+  => Setoid e a -> Setoid e a -> Setoid e a
+union = unionWith max
+
+-- | A generalized variant of union which accepts a function that will
+-- be used when two equivalent elements are found an the conflict
+-- needs to be resolved. Note that the elements are not necessarily
+-- equal
+unionWith
+  :: (Ord e)
+  => (a -> a -> a) -> Setoid e a -> Setoid e a -> Setoid e a
+unionWith f (Setoid x1) (Setoid x2) = Setoid (Map.unionWith f x1 x2)
+
+-- | Union several Setoids into one. This uses de default union
+-- variant
+unions
+  :: (Ord e, Ord a)
+  => [Setoid e a] -> Setoid e a
+unions = List.foldl' union empty
+
+-- ** Difference
+-- | Difference of two setoids. Return elements of the first setoid
+-- not existing in the second setoid.
+difference
+  :: (Ord e)
+  => Setoid e a -> Setoid e a -> Setoid e a
+difference (Setoid x) (Setoid y) = Setoid (Map.difference x y)
+
+-- ** Filter
+-- | Filter a setoid. Return a setoid with elements that statisfy the
+-- predicate
+filter
+  :: (Ord e)
+  => (a -> Bool) -> Setoid e a -> Setoid e a
+filter p (Setoid s) = Setoid (Map.filter p s)
+
+-- * Query
+-- | Test if Setoid is empty
+null :: Setoid e a -> Bool
+null (Setoid x) = Map.null x
+
+-- | Get the size of a setoid
+size :: Setoid e a -> Int
+size (Setoid x) = Map.size x
+
+-- | Test if an element is a member of a setoid
+member
+  :: (EquivalenceBy e a, Ord e)
+  => a -> Setoid e a -> Bool
+member e (Setoid x) = Map.member (eqRel e) x
+
+-- | Test if two Setoids are equivalent i.e. if all the elements are
+-- equivalent
+equivalence
+  :: (Eq e)
+  => Setoid e a -> Setoid e a -> Bool
+equivalence (Setoid x) (Setoid y) = Map.keys x == Map.keys y
+
+-- * Traversal
+-- | Map a function over elements of a setoid. It resolves conflict in
+-- the result by chosing the maximum one
+map
+  :: (EquivalenceBy eb b, Ord eb, Ord b)
+  => (a -> b) -> Setoid ea a -> Setoid eb b
+map f a = fromList (P.map f (toList a))
+
+-- | Generalized version of map, allowing to use custom function to
+-- resolve a conflict if two equivalent elements are found in the
+-- result
+mapResolve
+  :: (EquivalenceBy eb b, Ord eb)
+  => (b -> b -> b) -- ^ conflict resolution function
+  -> (a -> b)      -- ^ map function
+  -> Setoid ea a   -- ^ input
+  -> Setoid eb b   -- ^ result
+mapResolve r f a = fromListWith r (P.map f (toList a))
+
+-- | Monadic variant of a map
+mapM
+  :: (Monad m, EquivalenceBy eb b, Ord eb, Ord b)
+  => (a -> m b) -> Setoid ea a -> m (Setoid eb b)
+mapM f xs = fromList <$> P.mapM f (toList xs)
+
+-- * Conversion
+-- ** Lists
+-- | Convert setoid into a List
+toList :: Setoid e a -> [a]
+toList (Setoid a) = Map.elems a
+
+-- | A default variant of fromList using `max` to resolve a conflict
+-- if two equivalent elements are found. Therefore it depends on Ord
+-- instance of the element
+fromList
+  :: (EquivalenceBy e a, Ord e, Ord a)
+  => [a] -> Setoid e a
+fromList = fromListWith max
+
+-- | A generalized version of fromList, which will use a supplied
+-- funtion if two equivalent elements are found in the input list
+fromListWith
+  :: (EquivalenceBy e a, Ord e)
+  => (a -> a -> a) -> [a] -> Setoid e a
+fromListWith f = Setoid . Map.fromListWith f . P.map (\x -> (eqRel x, x))
+-- O(n+(n*log n))
+-- An implementation of List.foldl' (\a b -> union a (singleton b)) empty
+-- would be O(n*2n)
diff --git a/src/Data/Setoid/Equivalence.hs b/src/Data/Setoid/Equivalence.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Setoid/Equivalence.hs
@@ -0,0 +1,17 @@
+{-# LANGUAGE MultiParamTypeClasses #-}
+
+--------------------------------------------------------------------------------
+-- |
+-- Module      : Data.Setoid.Equivalence
+-- Copyright   : (c) Global Access Internet Services GmbH 2017
+-- License     : BSD3
+-- Maintainer  : Pavlo Kerestey <pavlo@kerestey.net>
+--------------------------------------------------------------------------------
+module Data.Setoid.Equivalence
+  ( EquivalenceBy(..)
+  ) where
+
+-- | Equivalence class. It reduces the data to the part which is
+-- then being tested for equality in a Setoid.
+class EquivalenceBy e a where
+  eqRel :: a -> e
diff --git a/src/Data/Setoid/Types.hs b/src/Data/Setoid/Types.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Setoid/Types.hs
@@ -0,0 +1,17 @@
+{-# LANGUAGE DeriveGeneric #-}
+
+--------------------------------------------------------------------------------
+-- |
+-- Module      : Data.Setoid.Types
+-- Copyright   : (c) Global Access Internet Services GmbH 2017
+-- License     : BSD3
+-- Maintainer  : Pavlo Kerestey <pavlo@kerestey.net>
+--------------------------------------------------------------------------------
+module Data.Setoid.Types where
+
+import           Data.Map.Strict (Map)
+import           GHC.Generics    (Generic)
+
+newtype Setoid e a =
+  Setoid (Map e a)
+  deriving (Eq, Ord, Generic)
diff --git a/tests/Data/SetoidTest.hs b/tests/Data/SetoidTest.hs
new file mode 100644
--- /dev/null
+++ b/tests/Data/SetoidTest.hs
@@ -0,0 +1,249 @@
+{-# LANGUAGE FlexibleContexts      #-}
+{-# LANGUAGE FlexibleInstances     #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE ScopedTypeVariables   #-}
+{-# OPTIONS_GHC -fno-warn-orphans #-}
+
+module Data.SetoidTest where
+
+import Control.Monad.Identity hiding (mapM)
+import Data.Setoid            hiding (ø, (\\), (∪))
+import Prelude                hiding (map, mapM, filter, null)
+import Test.SmallCheck.Series
+import Test.Tasty
+import Test.Tasty.HUnit
+import Test.Tasty.SmallCheck
+
+test_monoid_laws :: [TestTree]
+test_monoid_laws =
+  [ testProperty "associativity" (d3 (\a b c -> a <> (b <> c) == (a <> b) <> c))
+  , testProperty "left identity" (d4 (\a -> a <> ø == a))
+  , testProperty "right identity" (d4 (\a -> ø <> a == a))
+  ]
+
+test_construction :: [TestTree]
+test_construction =
+  [ testGroup "empty and singleton" $
+    [ testCase "empty" (ø @?= ø)
+    , testProperty "not empty" (\x -> st x /= ø)
+    , testProperty "equal to itself" (\x -> st x == st x)
+    , testProperty "different" (\x y -> x /= y ==> st x /= st y)
+    , testProperty "similar" (\x y -> x == y ==> st x == st y)
+    , testProperty
+        "transitive"
+        (d500
+           (over
+              similar3
+              (\(x, y, z) -> (x == y) && (y == z) ==> st (x, x) == st (z, z))))
+    ]
+  , testGroup "general union laws" $
+    [ testProperty "identity" (d3 (\a -> (a ∪ ø == a) && (ø ∪ a == a)))
+    , testProperty "reflexivity" (d3 (\a -> a ∪ a == a))
+    , testProperty "commutativity" (d3 (\a b -> a ∪ b == b ∪ a))
+    , testProperty "associativity" (d3 (\a b c -> a ∪ (b ∪ c) == (a ∪ b) ∪ c))
+    , testProperty "nonempty successor" (d3 (\a x -> a ∪ st x /= ø))
+    ]
+  , testGroup "unionWith" $
+    [ testProperty
+        "unionWith max behaves the same as union"
+        (d3 (\a b -> unionWith max a b == a ∪ b))
+    ]
+  , testGroup "unions" $
+    [ testCase "zero" (unions [ø] @?= ø)
+    , testProperty "one" (\a -> unions [st a] == st a)
+    , testProperty "many" (d3 (\a b -> unions (a ++ [b]) == unions a ∪ b))
+    ]
+  , testGroup "difference" $
+    [ testProperty "identity" (d3 (\a -> (a \\ ø == a) && (ø \\ a == ø)))
+    , testProperty
+        "by definition"
+        (d3
+           (\a b ->
+              all (\x -> (x `member` a) && not (x `member` b)) (toList (a \\ b))))
+    ]
+  , testGroup "filter" $
+    [ testProperty "empty" (d3 (\a -> (filter (const True) a == (a :: TestSetoid))))
+    , testProperty "empty" (d3 (\a -> (filter (const False) a == ø)))
+    , testProperty "by definition" $
+        let p = even . fst
+        in d4 $ \(a :: TestSetoid) ->
+           all (\x -> p x && (x `member` a)) (toList (filter p a))
+    ]
+  ]
+
+test_queries :: [TestTree]
+test_queries =
+  [ testGroup "null" $
+    [ testCase "empty" (null ø @?= True)
+    , testProperty "singleton" (\a -> null (st a) == False)
+    ]
+  , testGroup "size" $
+    [ testCase "empty" (size ø @?= 0)
+    , testProperty "singleton" (\a -> size (st a) == 1)
+    , testProperty
+        "non emtpy"
+        (d4 (\a -> size (a :: TestSetoid) == length (toList a)))
+    ]
+  , testGroup "member" $
+    [ testProperty "empty" (\x -> member x ø == False)
+    , testProperty "single element" (\x -> member x (st x) == True)
+    , testProperty "many elements" (d4 (\a x -> member x (a ∪ st x) == True))
+    , testProperty "many elements" (d4 (\a x -> member x (a \\ st x) == False))
+    ]
+  , testGroup "equivalence" $
+    [ testCase "empty case" (ø =~= ø @?= True)
+    , testProperty
+        "empty never equivalent to nonempty"
+        (\x -> not (ø =~= (st x)) && not (st x =~= ø))
+    , testProperty "singleton case" (\x -> st x =~= st x)
+    , testProperty
+        "not equivalent if eqRel is not equal"
+        (over
+           different
+           (\(x, y) ->
+              (eqRel x :: Int) /= (eqRel y :: Int) ==> not (st x =~= st y)))
+    , testProperty
+        "equivalent if eqRel is equal"
+        (over
+           similar
+           (\(x, y) -> (eqRel x :: Int) == (eqRel y :: Int) ==> st x =~= st y))
+    ]
+  ]
+
+test_traversal :: [TestTree]
+test_traversal =
+  [ testGroup "map" $
+    [ testProperty "∃ x ∈ a: ∀ y ∈ (map f a): f x == y" $
+      let f (x, y) = (x * y, x + y)
+      in (d4 $ forAll $ \(a :: TestSetoid) ->
+            (`all` toList (map f a :: TestSetoid)) $ \y ->
+                   any (\x -> f x == y) (toList a))
+    , testProperty "∀ x ∈ a: f x ∈ (map f a))" $
+      let f (x, y) = (x * y, x + y)
+      in (d4 $ forAll $ \(a :: TestSetoid) ->
+            (`all` toList a) $ \x ->
+                   f x `member` (map f a :: TestSetoid))
+    ]
+  , testGroup "mapResolve" $
+    [ testProperty "Is the same as map when chosing `max` as resolver" $
+      let f (x,y) = (x*y, x+y)
+      in d3 $ \(a :: TestSetoid) ->
+         mapResolve max f a == (map f a :: TestSetoid)
+    ]
+  , testGroup "mapM" $
+    [ testProperty "Results are equivalent to pure version. Note the ordering" $
+      let f (x,y) = (x*y, x+y)
+      in d3 $ \(a :: TestSetoid) ->
+         mapM (return . f) a `mEqual` return (map f a)
+    ]
+  ]
+
+test_conversion :: [TestTree]
+test_conversion =
+  [ testGroup "fromList" $
+    [ testCase "zero" (fromList [] @?= ø)
+    , testProperty "singleton" (\x -> fromList [x] =~= st x)
+    , testProperty
+        "via union"
+        (d3 (\xs ys -> fromList (xs ++ ys) == fromList xs ∪ fromList ys))
+    , testProperty
+        "transitivity"
+        (d3
+           (\xs ys zs ->
+              (fromList (xs ++ ys) ∪ fromList zs) =~=
+              (fromList xs ∪ fromList (ys ++ zs))))
+    ]
+  , testGroup "fromListWith" $
+    [ testProperty
+        "fromListWith max == fromList"
+        (d4
+           (\xs -> fromListWith max xs == (fromList xs :: TestSetoid)))
+    ]
+  , testGroup "toList" $
+    [testProperty "inverse to fromList" (d4 (toList `isInverseOf` fromList))]
+  ]
+
+instance EquivalenceBy k (k, v) where
+  eqRel = fst
+
+type TestSetoid = Setoid Int (Int, Int)
+
+st
+  :: EquivalenceBy Int (Int, Int)
+  => (Int, Int) -> Setoid Int (Int, Int)
+st = singleton
+
+ø :: TestSetoid
+ø = empty
+
+(<>) :: TestSetoid -> TestSetoid -> TestSetoid
+(<>) = mappend
+
+(∪) :: TestSetoid -> TestSetoid -> TestSetoid
+(∪) = union
+
+(\\) :: TestSetoid -> TestSetoid -> TestSetoid
+(\\) = difference
+
+different :: Series m ((Int, Int), (Int, Int))
+different =
+  generate (\d -> [((x - 1, y), (x + 1, y)) | x <- [0 .. d], y <- [0 .. 10]])
+
+similar :: Series m ((Int, Int), (Int, Int))
+similar =
+  generate
+    (\d -> [((x, y), (x, z)) | x <- [0 .. d], y <- [0 .. 5], z <- [4 .. 9]])
+
+similar3 :: Series m (Int, Int, Int)
+similar3 = generate (\d -> [(k, k, k) | k <- [0 .. d]])
+
+isInverseOf
+  :: (TestSetoid -> a)
+  -> (a -> TestSetoid)
+  -> TestSetoid
+  -> Bool
+isInverseOf f g a = (g . f) a == a
+
+instance (Monad m, Ord k, Ord v, Serial m v, EquivalenceBy k v) =>
+         Serial m (Setoid k v) where
+  series = fromList <$> series
+
+mEqual :: (Identity (TestSetoid))
+       -> (Identity (TestSetoid))
+       -> Bool
+mEqual f g = runIdentity f == runIdentity g
+
+d1
+  :: Testable m a
+  => a -> Property m
+d1 = changeDepth (const 1)
+
+d2
+  :: Testable m a
+  => a -> Property m
+d2 = changeDepth (const 2)
+
+d3
+  :: Testable m a
+  => a -> Property m
+d3 = changeDepth (const 3)
+
+d4
+  :: Testable m a
+  => a -> Property m
+d4 = changeDepth (const 4)
+
+d5
+  :: Testable m a
+  => a -> Property m
+d5 = changeDepth (const 5)
+
+d10
+  :: Testable m a
+  => a -> Property m
+d10 = changeDepth (const 10)
+
+d500
+  :: Testable m a
+  => a -> Property m
+d500 = changeDepth (const 500)
diff --git a/tests/RunTests.hs b/tests/RunTests.hs
new file mode 100644
--- /dev/null
+++ b/tests/RunTests.hs
@@ -0,0 +1,1 @@
+{-# OPTIONS_GHC -F -pgmF tasty-discover #-}
