-- Hoogle documentation, generated by Haddock
-- See Hoogle, http://www.haskell.org/hoogle/
-- | A Haskell implementation of setoid
--
-- 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
@package setoid
@version 0.1.0.0
module Data.Setoid.Types
newtype Setoid e a
Setoid :: (Map e a) -> Setoid e a
instance GHC.Generics.Generic (Data.Setoid.Types.Setoid e a)
instance (GHC.Classes.Ord a, GHC.Classes.Ord e) => GHC.Classes.Ord (Data.Setoid.Types.Setoid e a)
instance (GHC.Classes.Eq a, GHC.Classes.Eq e) => GHC.Classes.Eq (Data.Setoid.Types.Setoid e a)
module Data.Setoid.Equivalence
-- | Equivalence class. It reduces the data to the part which is then being
-- tested for equality in a Setoid.
class EquivalenceBy e a
eqRel :: EquivalenceBy e a => a -> e
-- | A Haskell implementation of 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 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
-- 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
data Setoid e a
-- | Equivalence class. It reduces the data to the part which is then being
-- tested for equality in a Setoid.
class EquivalenceBy e a
eqRel :: EquivalenceBy e a => a -> e
-- | Same as equivalence
(=~=) :: (Eq e) => Setoid e a -> Setoid e a -> Bool
infix 4 =~=
-- | Same as difference
(\\) :: (Ord e) => Setoid e a -> Setoid e a -> Setoid e a
infix 5 \\
-- | Same as union
(∪) :: (Ord e, Ord a) => Setoid e a -> Setoid e a -> Setoid e a
-- | An empty Setoid
empty :: Setoid e a
-- | Same as empty
ø :: Setoid e a
-- | A Setoid with a single element
singleton :: (EquivalenceBy e a) => a -> Setoid e a
-- | 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 several Setoids into one. This uses de default union variant
unions :: (Ord e, Ord a) => [Setoid e a] -> Setoid e a
-- | 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
-- | 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
-- | Filter a setoid. Return a setoid with elements that statisfy the
-- predicate
filter :: (Ord e) => (a -> Bool) -> Setoid e a -> Setoid e a
-- | Test if Setoid is empty
null :: Setoid e a -> Bool
-- | Get the size of a setoid
size :: Setoid e a -> Int
-- | Test if an element is a member of a setoid
member :: (EquivalenceBy e a, Ord e) => a -> Setoid e a -> Bool
-- | Test if two Setoids are equivalent i.e. if all the elements are
-- equivalent
equivalence :: (Eq e) => Setoid e a -> Setoid e a -> Bool
-- | 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
-- | 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) -> (a -> b) -> Setoid ea a -> Setoid eb b
-- | 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)
-- | 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
-- | 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
-- | Convert setoid into a List
toList :: Setoid e a -> [a]
instance GHC.Show.Show a => GHC.Show.Show (Data.Setoid.Types.Setoid e a)
instance (GHC.Classes.Ord e, GHC.Classes.Ord a) => GHC.Base.Monoid (Data.Setoid.Types.Setoid e a)