skeletal-set-0.1.0.0: Skeletal set - a set with equivalence relation different from equality

Copyright(c) Global Access Internet Services GmbH
LicenseBSD3
MaintainerPavlo Kerestey <pavlo@kerestey.net>
Safe HaskellSafe
LanguageHaskell2010

Data.SkeletalSet

Contents

Description

A Haskell implementation of skeletal set - a set equipped with an equivalence relation. SkeletalSet is a useful data structure when equivalence is chosen not to be equality. This allows to influence the membership of the elements in a set.

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 practical 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 skeletal set 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 is `SkeletalSet e a` where a is the member type and e is the type of equivalence intermediary. To chose the members of the skeletal set 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 SkeletalSet of fruit names by colour. We want one fruit per colour as a result and don't care if its apple or an orange.

import Data.SkeletalSet (SkeletalSet)
import qualified Data.SkeletalSet as SkeletalSet

data Colour = Red | Green | Blue deriving (Eq,Ord)

instance EquivalenceBy Colour (Colour,String) where
  eqRel = fst

apples, organges, fruits :: SkeletalSet Int (Int,String)
apples  = SkeletalSet.fromList [(Green,"golden delicious"), (Orange,"honeycrunch")]
oranges = SkeletalSet.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 SkeletalSet 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 :: SkeletalSet Email User
usersF <- getUsers F
usersG <- getUsers G

allUsers = SkeletalSet.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 sets - in this case they have the same email address. 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 both services.

Here is also one of the shortcomings of the library. mergeContactDetails choses the email of the first argument. Since 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.

SkeletalSet.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 skeletal sets and Quotient Sets. It seems, that a `SkeletalSet (a,b) (a,b,c)` is equivalent to a `QuotientSet a (SkeletalSet b (a,b,c))`. This means that every QuotientSet can actually be represented as a skeletal set.
  • Performance is another issue. Current implementation uses the `newtype SkeletalSet x y = SkeletalSet (Map x y)` which may be inefficient.

Synopsis

Type

data SkeletalSet e a Source #

Instances

(Eq a, Eq e) => Eq (SkeletalSet e a) Source # 

Methods

(==) :: SkeletalSet e a -> SkeletalSet e a -> Bool #

(/=) :: SkeletalSet e a -> SkeletalSet e a -> Bool #

(Ord a, Ord e) => Ord (SkeletalSet e a) Source # 

Methods

compare :: SkeletalSet e a -> SkeletalSet e a -> Ordering #

(<) :: SkeletalSet e a -> SkeletalSet e a -> Bool #

(<=) :: SkeletalSet e a -> SkeletalSet e a -> Bool #

(>) :: SkeletalSet e a -> SkeletalSet e a -> Bool #

(>=) :: SkeletalSet e a -> SkeletalSet e a -> Bool #

max :: SkeletalSet e a -> SkeletalSet e a -> SkeletalSet e a #

min :: SkeletalSet e a -> SkeletalSet e a -> SkeletalSet e a #

Generic (SkeletalSet e a) Source # 

Associated Types

type Rep (SkeletalSet e a) :: * -> * #

Methods

from :: SkeletalSet e a -> Rep (SkeletalSet e a) x #

to :: Rep (SkeletalSet e a) x -> SkeletalSet e a #

type Rep (SkeletalSet e a) Source # 
type Rep (SkeletalSet e a) = D1 * (MetaData "SkeletalSet" "Data.SkeletalSet.Types" "skeletal-set-0.1.0.0-JepxnULlTCFA7sYlAYzxTe" True) (C1 * (MetaCons "SkeletalSet" PrefixI False) (S1 * (MetaSel (Nothing Symbol) NoSourceUnpackedness NoSourceStrictness DecidedLazy) (Rec0 * (Map e a))))

Class

class EquivalenceBy e a where Source #

Equivalence class. It reduces the data to the part which is then being tested for equality in a SkeletalSet.

Minimal complete definition

eqRel

Methods

eqRel :: a -> e Source #

Operators

(=~=) :: Eq e => SkeletalSet e a -> SkeletalSet e a -> Bool infix 4 Source #

Same as equivalence

(\\) :: Ord e => SkeletalSet e a -> SkeletalSet e a -> SkeletalSet e a infix 5 Source #

Same as difference

(∪) :: (Ord e, Ord a) => SkeletalSet e a -> SkeletalSet e a -> SkeletalSet e a Source #

Same as union

Construction

empty :: SkeletalSet e a Source #

An empty SkeletalSet

ø :: SkeletalSet e a Source #

Same as empty

singleton :: EquivalenceBy e a => a -> SkeletalSet e a Source #

A SkeletalSet with a single element

union :: (Ord e, Ord a) => SkeletalSet e a -> SkeletalSet e a -> SkeletalSet e a Source #

Combine two SkeletalSets resolving conflicts with max by default. This makes the union operation commutative and associative.

unions :: (Ord e, Ord a) => [SkeletalSet e a] -> SkeletalSet e a Source #

Union several SkeletalSets into one. This uses de default union variant

unionWith :: Ord e => (a -> a -> a) -> SkeletalSet e a -> SkeletalSet e a -> SkeletalSet e a Source #

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

Difference

difference :: Ord e => SkeletalSet e a -> SkeletalSet e a -> SkeletalSet e a Source #

Difference of two skeletal sets. Return elements of the first skeletal sets not existing in the second set.

Filter

filter :: Ord e => (a -> Bool) -> SkeletalSet e a -> SkeletalSet e a Source #

Filter a skeletal set. Return a skeletal set with elements that statisfy the predicate

Query

null :: SkeletalSet e a -> Bool Source #

Test if SkeletalSet is empty

size :: SkeletalSet e a -> Int Source #

Get the size of a skeletal set

member :: (EquivalenceBy e a, Ord e) => a -> SkeletalSet e a -> Bool Source #

Test if an element is a member of a skeletal set

equivalence :: Eq e => SkeletalSet e a -> SkeletalSet e a -> Bool Source #

Test if two SkeletalSets are equivalent i.e. if all the elements are equivalent

Traversal

map

map :: (EquivalenceBy eb b, Ord eb, Ord b) => (a -> b) -> SkeletalSet ea a -> SkeletalSet eb b Source #

Map a function over elements of a skeletal set. It resolves conflict in the result by chosing the maximum one

mapResolve Source #

Arguments

:: (EquivalenceBy eb b, Ord eb) 
=> (b -> b -> b)

conflict resolution function

-> (a -> b)

map function

-> SkeletalSet ea a

input

-> SkeletalSet eb b

result

Generalized version of map, allowing to use custom function to resolve a conflict if two equivalent elements are found in the result

mapM :: (Monad m, EquivalenceBy eb b, Ord eb, Ord b) => (a -> m b) -> SkeletalSet ea a -> m (SkeletalSet eb b) Source #

Monadic variant of a map

Conversion

fromList :: (EquivalenceBy e a, Ord e, Ord a) => [a] -> SkeletalSet e a Source #

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

fromListWith :: (EquivalenceBy e a, Ord e) => (a -> a -> a) -> [a] -> SkeletalSet e a Source #

A generalized version of fromList, which will use a supplied funtion if two equivalent elements are found in the input list

toList :: SkeletalSet e a -> [a] Source #

Convert skeletal set into a List

Orphan instances

Show a => Show (SkeletalSet e a) Source #

Instance Show, used for debugging

Methods

showsPrec :: Int -> SkeletalSet e a -> ShowS #

show :: SkeletalSet e a -> String #

showList :: [SkeletalSet e a] -> ShowS #

(Ord e, Ord a) => Monoid (SkeletalSet e a) Source #

Monoid instance for SkeletalSet

Methods

mempty :: SkeletalSet e a #

mappend :: SkeletalSet e a -> SkeletalSet e a -> SkeletalSet e a #

mconcat :: [SkeletalSet e a] -> SkeletalSet e a #