packages feed

hjugement 1.0.0.20170808 → 2.0.0.20180903

raw patch · 13 files changed

+1468/−430 lines, 13 filesdep +hashabledep +randomdep +unordered-containersdep ~containersdep ~tasty-hunitdep ~textPVP ok

version bump matches the API change (PVP)

Dependencies added: hashable, random, unordered-containers

Dependency ranges changed: containers, tasty-hunit, text, transformers

API changes (from Hackage documentation)

- Hjugement: class IsList l where type Item l :: * where {
- Hjugement: fromList :: IsList l => [Item l] -> l
- Hjugement: fromListN :: IsList l => Int -> [Item l] -> l
- Hjugement: toList :: IsList l => l -> [Item l]
- Hjugement: type family Item l :: *;
- Hjugement: }
- Hjugement.Majority: Merit :: (Map grade Count) -> Merit grade
- Hjugement.Majority: Merits :: (Map prop (Merit grade)) -> Merits prop grade
- Hjugement.Majority: Opinions :: (Map judge (Opinion prop grade)) -> Opinions prop grade judge
- Hjugement.Majority: Scale :: Set grade -> grade -> Scale grade
- Hjugement.Majority: Value :: [(grade, Count)] -> Value grade
- Hjugement.Majority: [scaleDefault] :: Scale grade -> grade
- Hjugement.Majority: [scaleGrades] :: Scale grade -> Set grade
- Hjugement.Majority: choices :: (Bounded prop, Enum prop, Ord prop) => Choices prop
- Hjugement.Majority: data Scale grade
- Hjugement.Majority: gradeOfScale :: [a] -> Int -> a
- Hjugement.Majority: instance (GHC.Classes.Eq grade, GHC.Classes.Eq prop) => GHC.Classes.Eq (Hjugement.Majority.Merits prop grade)
- Hjugement.Majority: instance (GHC.Classes.Eq grade, GHC.Classes.Eq prop, GHC.Classes.Eq judge) => GHC.Classes.Eq (Hjugement.Majority.Opinions prop grade judge)
- Hjugement.Majority: instance (GHC.Classes.Ord grade, GHC.Classes.Ord prop) => Data.Semigroup.Semigroup (Hjugement.Majority.Merits prop grade)
- Hjugement.Majority: instance (GHC.Classes.Ord grade, GHC.Show.Show grade) => GHC.Exts.IsList (Hjugement.Majority.Merit grade)
- Hjugement.Majority: instance (GHC.Classes.Ord judge, GHC.Show.Show judge) => GHC.Exts.IsList (Hjugement.Majority.Opinions prop grade judge)
- Hjugement.Majority: instance (GHC.Classes.Ord prop, GHC.Show.Show prop) => GHC.Exts.IsList (Hjugement.Majority.Merits prop grade)
- Hjugement.Majority: instance (GHC.Show.Show grade, GHC.Show.Show prop) => GHC.Show.Show (Hjugement.Majority.Merits prop grade)
- Hjugement.Majority: instance (GHC.Show.Show grade, GHC.Show.Show prop, GHC.Show.Show judge) => GHC.Show.Show (Hjugement.Majority.Opinions prop grade judge)
- Hjugement.Majority: instance GHC.Classes.Eq grade => GHC.Classes.Eq (Hjugement.Majority.Merit grade)
- Hjugement.Majority: instance GHC.Classes.Eq grade => GHC.Classes.Eq (Hjugement.Majority.Scale grade)
- Hjugement.Majority: instance GHC.Classes.Eq grade => GHC.Classes.Eq (Hjugement.Majority.Value grade)
- Hjugement.Majority: instance GHC.Classes.Ord grade => Data.Semigroup.Semigroup (Hjugement.Majority.Merit grade)
- Hjugement.Majority: instance GHC.Classes.Ord grade => GHC.Classes.Ord (Hjugement.Majority.Merit grade)
- Hjugement.Majority: instance GHC.Classes.Ord grade => GHC.Classes.Ord (Hjugement.Majority.Value grade)
- Hjugement.Majority: instance GHC.Show.Show grade => GHC.Show.Show (Hjugement.Majority.Merit grade)
- Hjugement.Majority: instance GHC.Show.Show grade => GHC.Show.Show (Hjugement.Majority.Scale grade)
- Hjugement.Majority: instance GHC.Show.Show grade => GHC.Show.Show (Hjugement.Majority.Value grade)
- Hjugement.Majority: jury :: (Bounded judge, Enum judge, Ord judge) => Jury judge
- Hjugement.Majority: majorityGrade :: Ord grade => Merit grade -> grade
- Hjugement.Majority: majorityRanking :: Ord grade => Merits prop grade -> Ranking prop
- Hjugement.Majority: majorityValue :: Ord grade => Merit grade -> Value grade
- Hjugement.Majority: majorityValueByChoice :: Ord grade => Merits prop grade -> [(prop, Value grade)]
- Hjugement.Majority: merit :: (Ord grade, Ord prop) => Scale grade -> prop -> Opinions prop grade judge -> Merit grade
- Hjugement.Majority: merits :: (Ord grade, Ord prop) => Scale grade -> Choices prop -> Opinions prop grade judge -> Merits prop grade
- Hjugement.Majority: newtype Merit grade
- Hjugement.Majority: newtype Merits prop grade
- Hjugement.Majority: newtype Opinions prop grade judge
- Hjugement.Majority: newtype Value grade
- Hjugement.Majority: opinion :: (Enum prop, Bounded prop, Ord prop) => judge -> [grade] -> (judge, Opinion prop grade)
- Hjugement.Majority: scale :: (Bounded grade, Enum grade, Ord grade) => Scale grade
- Hjugement.Majority: scaleOfList :: Eq a => [a] -> a -> Scale Int
- Hjugement.Majority: type Choices prop = Set prop
- Hjugement.Majority: type Count = Int
- Hjugement.Majority: type Jury judge = Set judge
- Hjugement.Majority: type Opinion prop grade = Map prop grade
- Hjugement.Majority: type Ranking prop = [prop]
+ Majority.Gauge: MajorityGauge :: Share -> g -> Share -> MajorityGauge g
+ Majority.Gauge: Minus :: Sign
+ Majority.Gauge: Plus :: Sign
+ Majority.Gauge: [mgGrade] :: MajorityGauge g -> g
+ Majority.Gauge: [mgHigher] :: MajorityGauge g -> Share
+ Majority.Gauge: [mgLower] :: MajorityGauge g -> Share
+ Majority.Gauge: data MajorityGauge g
+ Majority.Gauge: data Sign
+ Majority.Gauge: instance GHC.Classes.Eq Majority.Gauge.Sign
+ Majority.Gauge: instance GHC.Classes.Eq g => GHC.Classes.Eq (Majority.Gauge.MajorityGauge g)
+ Majority.Gauge: instance GHC.Classes.Ord g => GHC.Classes.Ord (Majority.Gauge.MajorityGauge g)
+ Majority.Gauge: instance GHC.Show.Show Majority.Gauge.Sign
+ Majority.Gauge: instance GHC.Show.Show g => GHC.Show.Show (Majority.Gauge.MajorityGauge g)
+ Majority.Gauge: majorityGauge :: Ord grade => Merit grade -> Maybe (MajorityGauge grade)
+ Majority.Gauge: majorityGaugeRanking :: Ord grade => MeritByChoice choice grade -> MajorityGaugeRanking choice grade
+ Majority.Gauge: majorityGauges :: Ord grade => Merit grade -> [MajorityGauge grade]
+ Majority.Gauge: majorityGaugesByChoice :: Ord grade => MeritByChoice choice grade -> HashMap choice [MajorityGauge grade]
+ Majority.Gauge: mgSign :: MajorityGauge g -> Sign
+ Majority.Gauge: type MajorityGaugeRanking choice grade = [(choice, [MajorityGauge grade])]
+ Majority.Merit: Merit :: Map grade Share -> Merit grade
+ Majority.Merit: MeritByChoice :: HashMap choice (Merit grade) -> MeritByChoice choice grade
+ Majority.Merit: Ranked :: (Integer, a) -> Ranked a
+ Majority.Merit: [unMeritByChoice] :: MeritByChoice choice grade -> HashMap choice (Merit grade)
+ Majority.Merit: [unMerit] :: Merit grade -> Map grade Share
+ Majority.Merit: choices :: (Bounded choice, Enum choice, Eq choice, Hashable choice) => Choices choice
+ Majority.Merit: enum :: (Bounded a, Enum a, Ord a) => Set a
+ Majority.Merit: grades :: [grade] -> Grades (Ranked grade)
+ Majority.Merit: instance (GHC.Classes.Eq choice, Data.Hashable.Class.Hashable choice, GHC.Classes.Ord grade) => GHC.Base.Semigroup (Majority.Merit.MeritByChoice choice grade)
+ Majority.Merit: instance (GHC.Classes.Eq choice, Data.Hashable.Class.Hashable choice, GHC.Show.Show choice) => GHC.Exts.IsList (Majority.Merit.MeritByChoice choice grade)
+ Majority.Merit: instance (GHC.Classes.Eq choice, GHC.Classes.Eq grade) => GHC.Classes.Eq (Majority.Merit.MeritByChoice choice grade)
+ Majority.Merit: instance (GHC.Classes.Ord grade, GHC.Show.Show grade) => GHC.Exts.IsList (Majority.Merit.Merit grade)
+ Majority.Merit: instance (GHC.Show.Show choice, GHC.Show.Show grade) => GHC.Show.Show (Majority.Merit.MeritByChoice choice grade)
+ Majority.Merit: instance GHC.Base.Functor Majority.Merit.Ranked
+ Majority.Merit: instance GHC.Classes.Eq (Majority.Merit.Ranked a)
+ Majority.Merit: instance GHC.Classes.Eq grade => GHC.Classes.Eq (Majority.Merit.Merit grade)
+ Majority.Merit: instance GHC.Classes.Ord (Majority.Merit.Ranked a)
+ Majority.Merit: instance GHC.Classes.Ord grade => GHC.Base.Semigroup (Majority.Merit.Merit grade)
+ Majority.Merit: instance GHC.Show.Show a => GHC.Show.Show (Majority.Merit.Ranked a)
+ Majority.Merit: instance GHC.Show.Show grade => GHC.Show.Show (Majority.Merit.Merit grade)
+ Majority.Merit: judges :: Eq judge => Hashable judge => [judge] -> grade -> Judges judge grade
+ Majority.Merit: merit :: Ord grade => Opinions judge grade -> Merit grade
+ Majority.Merit: meritByChoice :: (Ord grade, Eq choice, Hashable choice) => OpinionsByChoice choice judge grade -> MeritByChoice choice grade
+ Majority.Merit: newtype Merit grade
+ Majority.Merit: newtype MeritByChoice choice grade
+ Majority.Merit: newtype Ranked a
+ Majority.Merit: opinions :: Eq judge => Hashable judge => Judges judge grade -> Opinions judge grade -> (Opinions judge grade, HashSet judge)
+ Majority.Merit: opinionsByChoice :: Eq choice => Hashable choice => [(choice, Opinions judge grade)] -> OpinionsByChoice choice judge grade
+ Majority.Merit: rankKey :: [(k, a)] -> [(Ranked k, a)]
+ Majority.Merit: singleGrade :: grade -> Distribution grade
+ Majority.Merit: type Choices = HashSet
+ Majority.Merit: type Distribution grade = Map grade Share
+ Majority.Merit: type Grades = Set
+ Majority.Merit: type Judges = HashMap
+ Majority.Merit: type Opinions judge grade = HashMap judge (Distribution grade)
+ Majority.Merit: type OpinionsByChoice choice judge grade = HashMap choice (Opinions judge grade)
+ Majority.Merit: type Share = Rational
+ Majority.Merit: unRank :: Ranked a -> a
+ Majority.Merit: zipRank :: [a] -> [Ranked a]
+ Majority.Section: ErrorSection_invalid_shares :: (HashMap choice (HashMap judge [Share])) -> ErrorSection choice judge grade
+ Majority.Section: ErrorSection_unknown_choices :: (HashSet choice) -> ErrorSection choice judge grade
+ Majority.Section: ErrorSection_unknown_judges :: (HashMap choice (HashSet judge)) -> ErrorSection choice judge grade
+ Majority.Section: Section :: Maybe Share -> Maybe grade -> Section grade
+ Majority.Section: SectionNode :: Maybe Share -> HashMap choice (SectionByJudge judge grade) -> SectionNode choice judge grade
+ Majority.Section: [sectionByJudgeByChoice] :: SectionNode choice judge grade -> HashMap choice (SectionByJudge judge grade)
+ Majority.Section: [sectionGrade] :: Section grade -> Maybe grade
+ Majority.Section: [sectionNodeShare] :: SectionNode choice judge grade -> Maybe Share
+ Majority.Section: [sectionShare] :: Section grade -> Maybe Share
+ Majority.Section: data ErrorSection choice judge grade
+ Majority.Section: data Section grade
+ Majority.Section: data SectionNode choice judge grade
+ Majority.Section: instance (GHC.Classes.Eq choice, GHC.Classes.Eq judge) => GHC.Classes.Eq (Majority.Section.ErrorSection choice judge grade)
+ Majority.Section: instance (GHC.Classes.Eq choice, GHC.Classes.Eq judge, GHC.Classes.Eq grade) => GHC.Classes.Eq (Majority.Section.SectionNode choice judge grade)
+ Majority.Section: instance (GHC.Show.Show choice, GHC.Show.Show judge) => GHC.Show.Show (Majority.Section.ErrorSection choice judge grade)
+ Majority.Section: instance (GHC.Show.Show choice, GHC.Show.Show judge, GHC.Show.Show grade) => GHC.Show.Show (Majority.Section.SectionNode choice judge grade)
+ Majority.Section: instance GHC.Classes.Eq grade => GHC.Classes.Eq (Majority.Section.Section grade)
+ Majority.Section: instance GHC.Show.Show grade => GHC.Show.Show (Majority.Section.Section grade)
+ Majority.Section: opinionsBySection :: forall choice judge grade. Eq choice => Eq judge => Hashable choice => Hashable judge => Ord grade => Choices choice -> Judges judge grade -> Tree (SectionNode choice judge grade) -> Either (ErrorSection choice judge grade) (Tree (OpinionsByChoice choice judge grade))
+ Majority.Section: type SectionByJudge judge grade = HashMap judge (Section grade)
+ Majority.Value: MajorityValue :: [Middle grade] -> MajorityValue grade
+ Majority.Value: Middle :: Share -> grade -> grade -> Middle grade
+ Majority.Value: [highGrade] :: Middle grade -> grade
+ Majority.Value: [lowGrade] :: Middle grade -> grade
+ Majority.Value: [middleShare] :: Middle grade -> Share
+ Majority.Value: [unMajorityValue] :: MajorityValue grade -> [Middle grade]
+ Majority.Value: data Middle grade
+ Majority.Value: instance (GHC.Show.Show grade, GHC.Classes.Ord grade) => GHC.Classes.Ord (Majority.Merit.Merit grade)
+ Majority.Value: instance GHC.Classes.Eq grade => GHC.Classes.Eq (Majority.Value.MajorityValue grade)
+ Majority.Value: instance GHC.Classes.Eq grade => GHC.Classes.Eq (Majority.Value.Middle grade)
+ Majority.Value: instance GHC.Classes.Ord grade => GHC.Classes.Ord (Majority.Value.MajorityValue grade)
+ Majority.Value: instance GHC.Classes.Ord grade => GHC.Classes.Ord (Majority.Value.Middle grade)
+ Majority.Value: instance GHC.Show.Show grade => GHC.Show.Show (Majority.Value.MajorityValue grade)
+ Majority.Value: instance GHC.Show.Show grade => GHC.Show.Show (Majority.Value.Middle grade)
+ Majority.Value: majorityGrade :: Show grade => Ord grade => Merit grade -> Maybe grade
+ Majority.Value: majorityRanking :: Show grade => Ord grade => MeritByChoice choice grade -> MajorityRanking choice grade
+ Majority.Value: majorityValue :: Ord grade => Merit grade -> MajorityValue grade
+ Majority.Value: majorityValueByChoice :: Show grade => Ord grade => MeritByChoice choice grade -> HashMap choice (MajorityValue grade)
+ Majority.Value: newtype MajorityValue grade
+ Majority.Value: type MajorityRanking choice grade = [(choice, MajorityValue grade)]

Files

− Hjugement.hs
@@ -1,7 +0,0 @@-module Hjugement- ( module Hjugement.Majority- , IsList(..)- ) where--import Hjugement.Majority-import GHC.Exts (IsList(..))
− Hjugement/Majority.hs
@@ -1,223 +0,0 @@-{-# LANGUAGE TypeFamilies #-}-module Hjugement.Majority where--import Data.Function (on)-import Data.List-import Data.Map.Strict (Map)-import Data.Maybe (fromMaybe)-import Data.Ord (Down(..))-import Data.Semigroup (Semigroup(..))-import Data.Set (Set)-import GHC.Exts (IsList(..))-import qualified Data.Map.Strict as Map-import qualified Data.Set as Set---- * Type 'Choices'-type Choices prop = Set prop---- | Return a set of 'Choices' by enumerating the alternatives of its type. Useful on sum types.-choices :: (Bounded prop , Enum prop , Ord prop) => Choices prop-choices = Set.fromList (enumFrom minBound)---- * Type 'Scale'-data Scale grade- =   Scale- {   scaleGrades :: Set grade-     -- ^ How many 'grade's should be used?-     -- A greater number of 'grade's permits a finer distinction but demands-     -- a higher degree of expertise and discernment.-     -- The optimal number is the highest number of 'grade's that constitutes a common language,-     -- that is, that allows 'judge's to make absolute judgments.-     -- Too little 'grade's may lead too often to ties.-     -- -     -- Note, however, that if the inputs or grades depend on the set of choices,-     -- i.e. if judges change their grades when choices are added or dropped,-     -- then the Arrow paradox cannot be avoided.-     -- To avoid this the scale must be fixed and absolute-     -- so that more or fewer choices does not change-     -- the inputs or messages of other choices.- ,   scaleDefault :: grade-     -- ^ For instance, when a 'judge' gives no 'grade' or has “no opinion”,-     -- 'scaleDefault' could mean that the 'judge' chooses “To Reject” the choice:-     -- the rationale being that a 'judge' having “no opinion”-     -- concerning a choice has not even taken the time to evaluate it-     -- and thus has implicitly rejected it.- } deriving (Eq, Show)---- | Return a 'Scale' by enumerating the alternatives of its type. Useful on sum types.-scale :: (Bounded grade, Enum grade, Ord grade) => Scale grade-scale = Scale { scaleGrades  = Set.fromList (enumFrom minBound)-              , scaleDefault = toEnum 0-              }---- | Return a 'Scale' from a list of 'grade's and a default 'grade'.--- Useful with 'grade's whose type has no 'Ord' instance--- or a different one than the one wanted.-scaleOfList :: Eq a => [a] -> a -> Scale Int-scaleOfList gs dg = Scale is di-	where-	is = fromList $ findIndices (const True) gs-	di = fromMaybe (error "default grade not in the scale") $ dg`elemIndex`gs--gradeOfScale :: [a] -> Int -> a-gradeOfScale = (!!)---- * Type 'Jury'-type Jury judge = Set judge---- | Return a 'Jury' by enumerating the alternatives of its type. Useful on sum types.-jury :: (Bounded judge , Enum judge , Ord judge) => Jury judge-jury = Set.fromList (enumFrom minBound)---- * Type 'Opinion'--- | Profile of opinions of one single 'judge' about some 'prop'ositions.-type Opinion prop grade = Map prop grade---- | Construct the 'Opinion' of a 'judge' about some 'prop'ositions implicit from their type.-opinion :: (Enum prop, Bounded prop, Ord prop) =>-           judge -> [grade] ->-           (judge, Opinion prop grade)-opinion j gs = (j, Map.fromList (zip (enumFrom minBound) gs))---- ** Type 'Opinions'--- | Profile of opinions of some 'judge's about some 'prop'ositions.-newtype Opinions prop grade judge = Opinions (Map judge (Opinion prop grade))- deriving (Eq, Show)-instance (Ord judge, Show judge) => IsList (Opinions prop grade judge) where-	type Item (Opinions prop grade judge) = (judge, Opinion prop grade)-	fromList = Opinions . Map.fromListWithKey-		 (\k _x _y -> error $ "duplicate opinion for judge: " <> show k)-	toList (Opinions os) = toList os---- * Type 'Merit'--- | Profile of merits about a choice.-newtype Merit grade = Merit (Map grade Count)- deriving (Eq, Show)-type Count = Int--instance Ord grade => Semigroup (Merit grade) where-	Merit x <> Merit y = Merit (Map.unionWith (+) x y)-instance Ord grade => Ord (Merit grade) where-	compare = compare `on` majorityValue-instance (Ord grade, Show grade) => IsList (Merit grade) where-	type Item (Merit grade) = (grade, Count)-	fromList = Merit . Map.fromListWithKey-		 (\g _x _y -> error $ "duplicate grade in merit: " <> show g)-	toList (Merit cs) = toList cs---- | @merit grad@ returns the 'Merit'--- of a single 'choice' by some 'judge's.-merit :: (Ord grade, Ord prop) =>-         Scale grade -> prop -> Opinions prop grade judge ->-         Merit grade-merit scal prop (Opinions os) = foldr insertOpinion defaultMerit os-	where-	insertOpinion op (Merit m) = Merit (Map.insertWith (+) g 1 m)-		where g = Map.findWithDefault (scaleDefault scal) prop op-	defaultMerit = Merit (const 0 `Map.fromSet` scaleGrades scal)---- ** Type 'Merits'--- | Profile of merits about some choices.-newtype Merits prop grade = Merits (Map prop (Merit grade))- deriving (Eq, Show)-instance (Ord grade, Ord prop) => Semigroup (Merits prop grade) where-	Merits x <> Merits y = Merits (Map.unionWith (<>) x y)-instance (Ord prop, Show prop) => IsList (Merits prop grade) where-	type Item (Merits prop grade) = (prop, Merit grade)-	fromList = Merits . Map.fromListWithKey-		 (\p _x _y -> error $ "duplicate choice in merits: " <> show p)-	toList (Merits cs) = toList cs---- | @merit scal props opins@ returns the 'Merits'--- of the 'Choices' 'props'--- as judged by the 'Opinions' 'opins'--- on the 'Scale' 'scal'.-merits :: (Ord grade, Ord prop) =>-          Scale grade -> Choices prop -> Opinions prop grade judge ->-          Merits prop grade-merits scal props (Opinions os) = foldr ((<>) . meritsFromOpinion) defaultMerits os-	where-	meritsFromOpinion = Merits . (Merit . (`Map.singleton` 1) <$>) . (<> defaultOpinion)-	defaultOpinion    = const (scaleDefault scal) `Map.fromSet` props-	defaultMerits     = Merits (const defaultMerit `Map.fromSet` props)-	defaultMerit      = Merit (const 0 `Map.fromSet` scaleGrades scal)---- * Type 'Value'--- | A 'Value' is a compressed list of 'grade's,--- where each 'grade' is associated with the 'Count'--- by which it would be replicated in situ if decompressed.-newtype Value grade = Value [(grade,Count)]- deriving (Eq, Show)--- | 'compare' lexicographically as if the 'Value's--- were decompressed.-instance Ord grade => Ord (Value grade) where-	Value []`compare`Value [] = EQ-	Value []`compare`Value ys | all ((==0) . snd) ys = EQ-	                          | otherwise            = LT-	Value xs`compare`Value [] | all ((==0) . snd) xs = EQ-	                          | otherwise            = GT-	sx@(Value ((x,cx):xs)) `compare` sy@(Value ((y,cy):ys)) =-		case cx`compare`cy of-		 _ | cx == 0 && cy == 0 -> Value xs`compare`Value ys-		 _ | cx <= 0 -> Value xs`compare`sy-		 _ | cy <= 0 -> sx`compare`Value ys-		 EQ -> x`compare`y <> Value xs`compare`Value ys-		 LT -> x`compare`y <> Value xs`compare`Value((y,cy-cx):ys)-		 GT -> x`compare`y <> Value((x,cx-cy):xs)`compare`Value ys---- | The 'majorityValue' is the list of the 'majorityGrade's--- of a choice, each one replicated their associated 'Count' times,--- from the most consensual to the least,--- ie. by removing the 'grade' of the previous 'majorityGrade'--- to compute the next.-majorityValue :: Ord grade => Merit grade -> Value grade-majorityValue (Merit m) = Value (go m)-	where-	go gs = case snd (Map.foldlWithKey untilMajGrade (0,[]) gs) of-	         [] -> []-	         gw@(g,_):_ -> gw:go (Map.delete g gs)-		where-		tot = sum gs-		untilMajGrade (t,[]) g c | 2*tc >= tot = (tc,[(g,c)])-		                         | otherwise   = (tc,[])-		                         where tc = t+c-		untilMajGrade acc _g _c = acc---- | The 'majorityGrade' is the lower middlemost--- (also known as median by experts) of the 'grade's--- given to a choice by the 'judge's.--- --- It is the highest 'grade' approved by an absolute majority of the 'judge's:--- more than 50% of the 'judge's give the choice at least a 'grade' of 'majorityGrade',--- but every 'grade' lower than 'majorityGrade' is rejected by an absolute majority--- Thus the 'majorityGrade' of a choice--- is the final 'grade' wished by the majority.------ The 'majorityGrade' is necessarily a word that belongs to 'grades',--- and it has an absolute meaning.------ When the number of 'judge's is even, there is a middle-interval--- (which can, of course, be reduced to a single 'grade'--- if the two middle 'grade's are the same),--- then the 'majorityGrade' is the lowest 'grade' of the middle-interval--- (the “lower middlemost” when there are two in the middle),--- which is the only one which respects consensus:--- any other choice whose grades are all within this middle-interval,--- has a 'majorityGrade' which is greater or equal to this lower middlemost.-majorityGrade :: Ord grade => Merit grade -> grade-majorityGrade m = fst (head gs) where Value gs = majorityValue m---- * Type 'Ranking'--type Ranking prop = [prop]---- | The 'majorityRanking' ranks all the choices on the basis of their 'grade's.------ Choice A ranks higher than choice B in the 'majorityRanking'--- if and only if A’s 'majorityValue' is lexicographically above B’s.--- There can be no tie unless two choices have precisely the same 'majorityValue's.-majorityRanking :: Ord grade => Merits prop grade -> Ranking prop-majorityRanking = map fst . sortBy (compare `on` Down . snd) . majorityValueByChoice--majorityValueByChoice :: Ord grade => Merits prop grade -> [(prop, Value grade)]-majorityValueByChoice (Merits ms) = Map.toAscList (majorityValue <$> ms)
+ Majority/Gauge.hs view
@@ -0,0 +1,100 @@+-- | WARNING: the 'MajorityGauge' is a simplified 'MajorityValue'+-- which is sufficient to determine the 'MajorityRanking'+-- when the number of judges is large.+-- It is an approximation, it can perfectly lead to a wrong ranking+-- wrt. the 'MajorityRanking' done by using 'majorityValue'.+module Majority.Gauge where++import Data.Bool+import Data.Eq (Eq(..))+import Data.Function (($), (.))+import Data.Functor ((<$>))+import Data.Maybe (Maybe(..), listToMaybe)+import Data.Ord (Ord(..), Ordering(..), Down(..))+import Data.Tuple (snd)+import Prelude (Num(..))+import Text.Show (Show(..), showParen, shows)+import qualified Data.HashMap.Strict as HM+import qualified Data.List as List+import qualified Data.Map.Strict as Map++import Majority.Merit++-- * Type 'MajorityGauge'+-- | The 'MajorityGauge' is a simplification of the 'majorityValue'+-- from which may be deduced the 'majorityRanking'+-- among the propositions in many cases;+-- in particular, when there are many judges.+-- +-- However, when two propositions are tied with the same 'MajorityGauge',+-- they are not necessarily tied according to their 'majorityValue's.+data MajorityGauge g+ =   MajorityGauge+ {   mgHigher :: Share -- ^ Number of 'grade's given which are better than 'mgGrade'.+ ,   mgGrade  :: g     -- ^ 'majorityGrade'.+ ,   mgLower  :: Share -- ^ Number of 'grade's given which are worse than 'mgGrade'.+ } deriving (Eq)+instance Show g => Show (MajorityGauge g) where+	showsPrec p (MajorityGauge b g w) = showParen (p >= 10) $ shows (b,g,w)++-- ** Type 'Sign'+data Sign = Minus | Plus+ deriving (Eq, Show)++-- | If 'mgHigher' is higher than 'mgLower'+-- then the 'majorityGrade' is completed by a 'Plus';+-- otherwise the 'majorityGrade' is completed by a 'Minus'.+mgSign :: MajorityGauge g -> Sign+mgSign g = if mgHigher g > mgLower g then Plus else Minus++-- | The 'MajorityGauge'-ranking, first tries to rank+-- according to the 'majorityGrade' 'mgGrade'.+--+-- If both 'MajorityGauge's have the same 'mgGrade',+-- it tries to rank according to the 'mgSign' of both 'MajorityGauge's:+-- a 'Plus' is ahead of a 'Minus'.+-- +-- If both 'mgSign' are 'Plus',+-- the one having the higher 'mgHigher' is ahead,+-- or if both 'mgSign' are 'Minus',+-- the one having the higher 'mgLower' is behind.+--+-- Otherwise, the 'MajorityGauge'-ranking is a tie.+instance Ord g => Ord (MajorityGauge g) where+	x `compare` y =+		case mgGrade x `compare` mgGrade y of+		 EQ ->+			case (mgSign x, mgSign y) of+			 (Minus, Plus)  -> LT+			 (Plus , Minus) -> GT+			 (Plus , Plus)  -> mgHigher x `compare` mgHigher y+			 (Minus, Minus) -> mgLower  x `compare` mgLower  y+		 o -> o++majorityGauge :: Ord grade => Merit grade -> Maybe (MajorityGauge grade)+majorityGauge = listToMaybe . majorityGauges++majorityGauges :: Ord grade => Merit grade -> [MajorityGauge grade]+majorityGauges (Merit m) = go Map.empty m+	where+	go done gs = case snd (Map.foldlWithKey untilMajGrade (0,[]) gs) of+	              []  -> []+	              (mg,c):_ -> add mg done:go (Map.insert (mgGrade mg) c done) (Map.delete (mgGrade mg) gs)+		where+		add = Map.foldrWithKey $ \g c (MajorityGauge b mg w) ->+			if g >= mg then MajorityGauge (b+c) mg w+			           else MajorityGauge b mg (w+c)+		total = List.sum gs+		untilMajGrade (t,[]) g c | 2*tc >= total = (tc,[(MajorityGauge 0 g t,c)])+		                         | otherwise     = (tc,[])+		                         where tc = t+c+		untilMajGrade (t,(mg,c):_) _g c' = (t,[(mg{mgHigher=mgHigher mg + c'},c)])++-- * Type 'MajorityGaugeRanking'+type MajorityGaugeRanking choice grade = [(choice, [MajorityGauge grade])]++majorityGaugesByChoice :: Ord grade => MeritByChoice choice grade -> HM.HashMap choice [MajorityGauge grade]+majorityGaugesByChoice (MeritByChoice ms) = majorityGauges <$> ms++majorityGaugeRanking :: Ord grade => MeritByChoice choice grade -> MajorityGaugeRanking choice grade+majorityGaugeRanking = List.sortOn (Down . snd) . HM.toList . majorityGaugesByChoice
+ Majority/Judgment.hs view
@@ -0,0 +1,11 @@+module Majority.Judgment+ ( module Majority.Merit+ , module Majority.Value+ , module Majority.Gauge+ , module Majority.Section+ ) where++import Majority.Merit+import Majority.Value+import Majority.Gauge+import Majority.Section
+ Majority/Merit.hs view
@@ -0,0 +1,194 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE TypeFamilies #-} -- NOTE: for IsList+module Majority.Merit where++import Data.Eq (Eq(..))+import Data.Function (($), (.))+import Data.Functor (Functor, (<$>), (<$))+import Data.Hashable (Hashable)+import Data.List as List+import Data.Map.Strict (Map)+import Data.Ord (Ord(..))+import Data.Ratio (Rational)+import Data.Semigroup (Semigroup(..))+import Data.Set (Set)+import Data.Tuple (curry)+import GHC.Exts (IsList(..))+import Prelude (Bounded(..), Enum(..), Num(..), Integer, error)+import Text.Show (Show(..))+import qualified Data.HashMap.Strict as HM+import qualified Data.HashSet as HS+import qualified Data.Map.Strict as Map+import qualified Data.Set as Set++-- * Type 'Choices'+type Choices = HS.HashSet++-- | Return a set of 'Choices' by enumerating the alternatives of its type. Useful on sum types.+choices :: (Bounded choice , Enum choice , Eq choice, Hashable choice) => Choices choice+choices = HS.fromList $ enumFrom minBound++-- * Type 'Grades'+-- | How many 'grade's should be used?+-- A greater number of 'grade's permits a finer distinction but demands+-- a higher degree of expertise and discernment.+-- The optimal number is the highest number of 'grade's that constitutes a common language,+-- that is, that allows 'Judges' to make absolute judgments.+-- Too little 'grade's may lead too often to ties.+-- +-- Note, however, that if the inputs or grades depend on the set of 'choice's,+-- i.e. if judges change their grades when 'choice's are added or dropped,+-- then the Arrow paradox cannot be avoided.+-- To avoid this the scale of grades must be fixed and absolute+-- so that more or fewer 'choice's does not change+-- the inputs or messages of other 'choice's.+type Grades = Set++grades :: [grade] -> Grades (Ranked grade)+grades = Set.fromList . zipRank++-- * Type 'Ranked'+-- | Helper type to rank data without a good 'Ord' instance.+newtype Ranked a = Ranked (Integer, a)+ deriving (Show,Functor)+instance Eq (Ranked a) where+	Ranked (x,_) == Ranked (y,_) = x==y+instance Ord (Ranked a) where+	Ranked (x,_) `compare` Ranked (y,_) = x`compare`y++-- | @'zipRank' xs@ returns a list with the items of 'xs' wrapped+-- so that they are 'compare'able according to their position into 'xs'.+zipRank :: [a] -> [Ranked a]+zipRank = List.zipWith (curry Ranked) [0..]++rankKey :: [(k, a)] -> [(Ranked k, a)]+rankKey = List.zipWith (\i (k,a) -> (Ranked (i,k),a)) [0..]++unRank :: Ranked a -> a+unRank (Ranked (_i, x)) = x++-- | Return the 'Set' enumerating the alternatives+-- of its type parameter. Useful on sum types.+enum :: (Bounded a, Enum a, Ord a) => Set a+enum = Set.fromList $ enumFrom minBound++-- * Type 'Judges'+-- | Map each 'judge' to its default 'grade'+-- (usually the same for all 'judge's but not necessarily).+--+-- For instance, when a 'judge' gives no 'grade' or has “no opinion”,+-- this default grade could mean that the 'judge' chooses “To Reject” the 'choice':+-- the rationale being that a 'judge' having “no opinion”+-- concerning a 'choice' has not even taken the time to evaluate it+-- and thus has implicitly rejected it.+type Judges = HM.HashMap++judges ::+ Eq judge =>+ Hashable judge =>+ [judge] -> grade -> Judges judge grade+judges js dg = HM.fromList $ (\j -> (j, dg)) <$> js++-- * Type 'Opinions'+-- | Profile of opinions of some 'judge's about a single 'choice'.+type Opinions judge grade = HM.HashMap judge (Distribution grade)++-- | @(ok, ko) = 'opinions' js os@ returns:+--+-- * in 'ok' the opinions of the 'judge's 'js' updated by those in 'os',+-- * in 'ko' the opinions of 'judge's not in 'js'.+opinions ::+ Eq judge =>+ Hashable judge =>+ Judges judge grade ->+ Opinions judge grade ->+ ( Opinions judge grade+ , HS.HashSet judge )+opinions js os =+	( HM.union os $ singleGrade <$> js+	, HS.fromMap $ (() <$) $ os`HM.difference`js )++-- ** Type 'Distribution'+-- | Usually, a 'judge' gives a 'singleGrade' to a given 'choice'.+-- However, when applying the Majority Judgment to a 'Tree' of 'Section's,+-- what a 'judge' gives to a parent 'Section'+-- is composed by the 'grade's he or she has given+-- to the sub-'Section's, and those can be different.+-- In that case, each 'grade' given to a sub-'Section' contributes to a 'Share'+-- of the parent 'Section' which therefore is not necessarily a 'singleGrade',+-- but more generally a 'Distribution' of 'grade's.+-- And the sub-'Section's can actually themselves have sub-'Section's,+-- hence not being given a 'grade', but a 'Distribution' of 'grade's too.+type Distribution grade = Map grade Share++singleGrade :: grade -> Distribution grade+singleGrade = (`Map.singleton` 1)++-- *** Type 'Share'+-- | Usually a 'judge' attributes a 'singleGrade' to a given 'choice',+-- and then the 'Share' of this 'grade' is 1.+-- However, when introducing vote colleges (giving more power to some 'judge's),+-- or when introducing 'Section's (decomposing a judgment into several sub-judgments),+-- it becomes possible that only a percentage of 'grade'+-- is attributed by a 'judge' to a given 'choice'.+-- This is what a 'Share' is used for.+type Share = Rational+ -- FIXME: newtype checking >= 0++-- ** Type 'OpinionsByChoice'+-- | Profile of opinions of some 'Judges' about some 'choice's.+type OpinionsByChoice choice judge grade = HM.HashMap choice (Opinions judge grade)++opinionsByChoice ::+ Eq choice =>+ Hashable choice =>+ [(choice, Opinions judge grade)] ->+ OpinionsByChoice choice judge grade+opinionsByChoice = HM.fromList++-- * Type 'Merit'+-- | Profile of merit about a single 'choice'.+newtype Merit grade = Merit { unMerit :: Map grade Share }+ deriving (Eq, Show)+instance Ord grade => Semigroup (Merit grade) where+	Merit x <> Merit y = Merit (Map.unionWith (+) x y)+instance (Ord grade, Show grade) => IsList (Merit grade) where+	type Item (Merit grade) = (grade, Share)+	fromList = Merit . Map.fromListWithKey+		 (\g _x _y -> error $ "duplicate grade in merit: " <> show g)+	toList (Merit cs) = toList cs++-- | @merit os@ returns the 'Merit' given by opinions 'os'+merit ::+ Ord grade =>+ Opinions judge grade ->+ Merit grade+merit = foldr insertOpinion $ Merit $ Map.empty+	-- TODO: maybe count by making g passes+	where+	insertOpinion dist (Merit m) =+		Merit $+		Map.foldlWithKey+		 (\acc g s -> Map.insertWith (+) g s acc)+		 m dist++-- ** Type 'MeritByChoice'+-- | Profile of merit about some 'choice's.+newtype MeritByChoice choice grade+ =      MeritByChoice { unMeritByChoice :: HM.HashMap choice (Merit grade) }+ deriving (Eq, Show)+instance (Eq choice, Hashable choice, Ord grade) => Semigroup (MeritByChoice choice grade) where+	MeritByChoice x <> MeritByChoice y = MeritByChoice (HM.unionWith (<>) x y)+instance (Eq choice, Hashable choice, Show choice) => IsList (MeritByChoice choice grade) where+	type Item (MeritByChoice choice grade) = (choice, Merit grade)+	fromList = MeritByChoice . HM.fromListWith+		 (\_x _y -> error $ "duplicate choice in merits")+	toList (MeritByChoice cs) = toList cs++-- | @meritByChoice gs cs os@ returns the 'Merit's+-- given to 'choice's 'cs' by opinions 'os' from the 'Judges' 'js' on the 'Grades' 'gs'.+meritByChoice ::+ (Ord grade, Eq choice, Hashable choice) =>+ OpinionsByChoice choice judge grade ->+ MeritByChoice choice grade+meritByChoice os = MeritByChoice $ merit <$> os
+ Majority/Section.hs view
@@ -0,0 +1,189 @@+{-# LANGUAGE ScopedTypeVariables #-}+-- | This module implements the composition of a Majority Judgment+-- from a tree of Majority Judgments: for the same question,+-- the same choices, the same judges and the same grades.+-- In that tree, a parent judgment is formed by the aggregation of its children judgments,+-- where a child judgment contributes only for a percentage of the parent judgment.+module Majority.Section where++import Control.Applicative (Applicative(..), Alternative(..))+import Data.Bool+import Data.Either (Either(..))+import Data.Eq (Eq(..))+import Data.Foldable (Foldable(..), any)+import Data.Function (($), (.))+import Data.Functor ((<$>), (<$))+import Data.Hashable (Hashable(..))+import Data.Maybe (Maybe(..), isNothing, maybe, fromMaybe)+import Data.Ord (Ord(..))+import Data.Traversable (Traversable(..))+import Data.Tree as Tree+import Prelude (Num(..), Fractional(..), toRational)+import Text.Show (Show(..))+import qualified Data.HashMap.Strict as HM+import qualified Data.HashSet as HS+import qualified Data.List as List+import qualified Data.Map.Strict as Map++import Majority.Merit++-- * Type 'Section'+-- | An opinion of a 'judge' about a 'choice' at a specific section 'Tree.Node'.+data Section grade+ =   Section+ {   sectionShare :: Maybe Share+     -- ^ A 'Share' within the parent 'Tree.Node'+     --   (defaulting to a 'Share' computed as the remaining 'Share' to reach 1+     --   divided by the number of defaulted 'Share's).+ ,   sectionGrade :: Maybe grade+     -- ^ A 'grade' attributed to the current 'Tree.Node'+     --   (defaulting to the 'grade' set on an ancestor 'Tree.Node' if any,+     --   or the |judge|'s default grade).+ } deriving (Eq,Show)++-- ** Type 'SectionByJudge'+type SectionByJudge judge grade = HM.HashMap judge (Section grade)++-- ** Type 'SectionNode'+-- | Node value of a 'Tree' holding a 'Section', per 'judge', per 'choice'.+data SectionNode choice judge grade+ =   SectionNode+ {   sectionNodeShare       :: Maybe Share+     -- ^ A default 'sectionShare' for judges not specifying their own.+ ,   sectionByJudgeByChoice :: HM.HashMap choice (SectionByJudge judge grade)+ } deriving (Eq,Show)++-- * Type 'ErrorSection'+data ErrorSection choice judge grade+ =   ErrorSection_unknown_choices (HS.HashSet choice)+     -- ^ When some 'choice's are not known.+ |   ErrorSection_unknown_judges (HM.HashMap choice (HS.HashSet judge))+     -- ^ When some 'judge's are not known.+ |   ErrorSection_invalid_shares (HM.HashMap choice (HM.HashMap judge [Share]))+     -- ^ When at least one of the 'Share's is not positive, or when their sum is not 1.+ deriving (Eq,Show)++-- | @'opinionsBySection' cs js ss@ computes the 'Opinions' of the given 'Judges' @js@ about the given 'choice's @cs@,+-- from the 'grade' (specified or omitted) attributed to 'choice's+-- and the 'Share's (specified or omitted) attributed to 'Tree.Node'+-- in given 'Tree' @ss@.+opinionsBySection ::+ forall choice judge grade.+ Eq choice =>+ Eq judge =>+ Hashable choice =>+ Hashable judge =>+ Ord grade =>+ Choices choice ->+ Judges judge grade ->+ Tree (SectionNode choice judge grade) ->+ Either (ErrorSection choice judge grade)+        (Tree (OpinionsByChoice choice judge grade))+opinionsBySection cs js = go ((singleGrade <$> js) <$ HS.toMap cs)+	where+	go :: HM.HashMap choice (HM.HashMap judge (Distribution grade)) ->+	      Tree (SectionNode choice judge grade) ->+	      Either (ErrorSection choice judge grade)+	             (Tree (OpinionsByChoice choice judge grade))+	go defaultDistJC (Tree.Node (SectionNode _sectionNodeShare currOpinJC) childOpinJCS) =+		-- From current |Tree.Node|'s value.+			let currDistJC :: HM.HashMap choice (HM.HashMap judge (Distribution grade)) =+				-- Collect the 'Distribution' of current 'Tree.Node',+				-- and insert default 'Distribution'+				-- for each unspecified 'judge'+				-- of each (specified or unspecified) 'choice'.+				let specifiedDistJC =+					HM.mapWithKey (\choice ->+						let defaultDistJ = defaultDistJC HM.!choice in+						HM.mapWithKey (\judge ->+							maybe (defaultDistJ HM.!judge) singleGrade .+							sectionGrade))+					 currOpinJC+				in+				HM.unionWith HM.union+				 specifiedDistJC+				 defaultDistJC+			in+		-- From children 'Tree.Node's.+			let maybeChildShareSJC :: HM.HashMap choice (HM.HashMap judge [Maybe Share]) =+				-- Collect the (specified or explicitely (with 'Nothing') unspecified) 'Share's by section,+				-- and insert all unspecified 'Share's when a 'choice' or a 'judge' is unspecified.+				foldr (\(Tree.Node SectionNode{sectionNodeShare, sectionByJudgeByChoice} _) ->+					let defaultChildShareSJC = ([sectionNodeShare] <$ js) <$ defaultDistJC in+					let specifiedChildShareSJC =+						(<$> sectionByJudgeByChoice) $+						(pure . (<|> sectionNodeShare) . sectionShare <$>) in+					-- Fusion specified 'choice's into accum.+					HM.unionWith (HM.unionWith (List.++)) $+						-- Add default 'Share' for this 'Tree.Node',+						-- for each unspecified 'judge' of specified and unspecified 'choice'.+						HM.unionWith HM.union+						 specifiedChildShareSJC+						 defaultChildShareSJC)+				 HM.empty+				 childOpinJCS+			in+			let childShareSJC :: HM.HashMap choice (HM.HashMap judge [Share]) =+				-- Replace unspecified shares of each child 'Tree.Node'+				-- by an even default: the total remaining 'Share'+				-- divided by the number of unspecified 'Share's.+				(<$> maybeChildShareSJC) $ \maybeShareSJ ->+					(<$> maybeShareSJ) $ \maybeShareS ->+						let specifiedShare    = sum $ fromMaybe 0 <$> maybeShareS in+						let unspecifiedShares = toRational $ List.length $ List.filter isNothing maybeShareS in+						let defaultShare      = (1 - specifiedShare) / unspecifiedShares in+						fromMaybe defaultShare <$> maybeShareS+			in+		case childOpinJCS of+		-- Test for unknown choices.+		 _ | unknownChoices <- currOpinJC`HM.difference`defaultDistJC+		   , not $ null unknownChoices ->+			Left $ ErrorSection_unknown_choices $+				HS.fromMap $ (() <$) $ unknownChoices+		-- Test for unknown judges.+		 _ | unknownJudgesC <- HM.filter (not . null) $+		                       HM.intersectionWith HM.difference+		                        currOpinJC+		                        defaultDistJC+		   , not $ null unknownJudgesC ->+			Left $ ErrorSection_unknown_judges $+				HS.fromMap . (() <$) <$> unknownJudgesC+		-- Handle no child 'Tree.Node':+		-- current 'Distribution' is computed from current |Tree.Node|'s value ('currOpinJC')+		-- and inherited default 'Distribution' ('defaultDistJC').+		 [] -> Right $ Tree.Node currDistJC []+		-- Test for invalid shares.+		 _ | invalidSharesJC <-+		       HM.filter (not . null) $+		       HM.filter (\ss -> any (< 0) ss || sum ss /= 1)+		       <$> childShareSJC+		   , not $ null invalidSharesJC ->+			Left $ ErrorSection_invalid_shares invalidSharesJC+		-- Handle children 'Tree.Node's:+		-- current 'Opinions' is computed from the 'Opinions' of the children 'Tree.Node's.+		 _ -> do+			distJCS :: [Tree (HM.HashMap choice (HM.HashMap judge (Distribution grade)))] <-+				traverse (go $ currDistJC) childOpinJCS+				-- 'grade's set at current 'Tree.Node' ('currDistJC')+				-- become the new default 'grade's ('defaultDistJC')+				-- within its children 'Tree.Node's.+			let distSJC :: HM.HashMap choice (HM.HashMap judge [Distribution grade]) =+				-- Collect the 'Distribution's by section.+				foldr (\distJC ->+					let newDistSJC = (pure <$>) <$> rootLabel distJC in+					HM.unionWith (HM.unionWith (List.++)) newDistSJC)+				 HM.empty+				 distJCS+			let distJC :: HM.HashMap choice (HM.HashMap judge (Distribution grade)) =+				-- Compute the current 'Distribution' by scaling (share *) and merging (+)+				-- the children 'Distribution's.+				HM.mapWithKey (\choice ->+					let childShareSJ = childShareSJC HM.!choice in+					HM.mapWithKey (\judge ->+						let childShareS = childShareSJ HM.!judge in+						Map.unionsWith (+) .+						List.zipWith+						 (\share dist -> (share *) <$> dist)+						 childShareS))+				 distSJC+			Right $ Tree.Node distJC distJCS
+ Majority/Value.hs view
@@ -0,0 +1,133 @@+{-# OPTIONS_GHC -fno-warn-orphans #-}+module Majority.Value where++import Data.Bool+import Data.Eq (Eq(..))+import Data.Function (($), (.), on)+import Data.Functor ((<$>))+import Data.List as List+import Data.Maybe (Maybe(..), listToMaybe)+import Data.Ord (Ord(..), Ordering(..), Down(..))+import Data.Ratio ((%))+import Data.Semigroup (Semigroup(..))+import Data.Tuple (snd)+import Prelude (Num(..))+import Text.Show (Show(..))+import qualified Data.HashMap.Strict as HM+import qualified Data.Map.Strict as Map++import Majority.Merit++-- * Type 'MajorityValue'+-- | A 'MajorityValue' is a list of 'grade's+-- made from the successive lower middlemosts of a 'Merit',+-- i.e. from the most consensual 'majorityGrade' to the least.+newtype MajorityValue grade = MajorityValue { unMajorityValue :: [Middle grade] }+ deriving (Eq, Show)+instance Ord grade => Ord (MajorityValue grade) where+	MajorityValue []`compare`MajorityValue [] = EQ+	MajorityValue []`compare`MajorityValue ys | all ((==0) . middleShare) ys = EQ+	                                          | otherwise                    = LT+	MajorityValue xs`compare`MajorityValue [] | all ((==0) . middleShare) xs = EQ+	                                          | otherwise                    = GT+	mx@(MajorityValue (x:xs)) `compare` my@(MajorityValue (y:ys))+	 | middleShare x <= 0 && middleShare y <= 0 = MajorityValue xs`compare`MajorityValue ys+	 | middleShare x <= 0 = MajorityValue xs`compare`my+	 | middleShare y <= 0 = mx`compare`MajorityValue ys+	 | otherwise =+		lowGrade x`compare`lowGrade y <>+		highGrade x`compare`highGrade y <>+		case middleShare x`compare`middleShare y of+		 LT -> compare (MajorityValue xs) (MajorityValue (y{middleShare = middleShare y - middleShare x} : ys))+		 EQ -> compare (MajorityValue xs) (MajorityValue ys)+		 GT -> compare (MajorityValue (x{middleShare = middleShare x - middleShare y} : xs)) (MajorityValue ys)++-- ** Type 'Middle'+-- | A centered middle of a 'Merit'.+-- Needed to handle the 'Fractional' capabilities of a 'Share'.+--+-- By construction in 'majorityValue',+-- 'lowGrade' is always lower or equal to 'highGrade'.+data Middle grade = Middle+ { middleShare :: Share -- ^ the same 'Share' of 'lowGrade' and 'highGrade'.+ , lowGrade    :: grade+ , highGrade   :: grade+ } deriving (Eq, Ord, Show)++-- | The 'majorityValue' is the list of the 'Middle's of the 'Merit' of a 'choice',+-- from the most consensual to the least.+majorityValue :: Ord grade => Merit grade -> MajorityValue grade+majorityValue (Merit countByGrade) = MajorityValue $ goMiddle 0 [] $ Map.toList countByGrade+	where+	total = sum countByGrade+	middle = (1%2) * total+	goMiddle :: Ord grade => Share -> [(grade,Share)] -> [(grade,Share)] -> [Middle grade]+	goMiddle prevShare ps next =+		case next of+		 [] -> []+		 curr@(currGrade,currShare):ns ->+			let nextShare = prevShare + currShare in+			case nextShare`compare`middle of+			 LT -> goMiddle nextShare (curr:ps) ns+			 EQ -> goBorders (curr:ps) ns+			 GT ->+				let lowShare  = middle - prevShare in+				let highShare = nextShare - middle in+				let minShare  = min lowShare highShare in+				Middle minShare currGrade currGrade :+				goBorders+				 ((currGrade, lowShare  - minShare) : ps)+				 ((currGrade, highShare - minShare) : ns)+	goBorders :: [(grade,Share)] -> [(grade,Share)] -> [Middle grade]+	goBorders lows highs =+		case (lows,highs) of+		 ((lowGrade,lowShare):ls, (highGrade,highShare):hs)+		  | lowShare <= 0  -> goBorders ls highs+		  | highShare <= 0 -> goBorders lows hs+		  | otherwise ->+				let minShare = min lowShare highShare in+				Middle minShare lowGrade highGrade :+				goBorders+				 ((lowGrade , lowShare  - minShare) : ls)+				 ((highGrade, highShare - minShare) : hs)+		 _ -> []+instance (Show grade, Ord grade) => Ord (Merit grade) where+	compare = compare `on` majorityValue++-- | The 'majorityGrade' is the lower middlemost+-- (also known as median by experts) of the 'grade's+-- given to a 'choice' by the 'Judges'.+--+-- It is the highest 'grade' approved by an absolute majority of the 'Judges':+-- more than 50% of the 'Judges' give the 'choice' at least a 'grade' of 'majorityGrade',+-- but every 'grade' lower than 'majorityGrade' is rejected by an absolute majority+-- Thus the 'majorityGrade' of a 'choice'+-- is the final 'grade' wished by the majority.+--+-- The 'majorityGrade' is necessarily a word that belongs to 'grades',+-- and it has an absolute meaning.+--+-- When the number of 'Judges' is even, there is a middle-interval+-- (which can, of course, be reduced to a single 'grade'+-- if the two middle 'grade's are the same),+-- then the 'majorityGrade' is the lowest 'grade' of the middle-interval+-- (the “lower middlemost” when there are two in the middle),+-- which is the only one which respects consensus:+-- any other 'choice' whose grades are all within this middle-interval,+-- has a 'majorityGrade' which is greater or equal to this lower middlemost.+majorityGrade :: Show grade => Ord grade => Merit grade -> Maybe grade+majorityGrade m = lowGrade <$> listToMaybe gs where MajorityValue gs = majorityValue m++-- * Type 'MajorityRanking'+type MajorityRanking choice grade = [(choice, MajorityValue grade)]++majorityValueByChoice :: Show grade => Ord grade => MeritByChoice choice grade -> HM.HashMap choice (MajorityValue grade)+majorityValueByChoice (MeritByChoice ms) = majorityValue <$> ms++-- | The 'majorityRanking' ranks all the 'choice's on the basis of their 'grade's.+--+-- Choice A ranks higher than 'choice' B in the 'majorityRanking'+-- if and only if A’s 'majorityValue' is lexicographically above B’s.+-- There can be no tie unless two 'choice's have precisely the same 'majorityValue's.+majorityRanking :: Show grade => Ord grade => MeritByChoice choice grade -> MajorityRanking choice grade+majorityRanking = List.sortOn (Down . snd) . HM.toList . majorityValueByChoice
README.md view
@@ -43,14 +43,14 @@ If the number of individual judgments is small and even (eg. for 6 judges: [“Insufficient”, “Acceptable”, “Acceptable”, “Good”, “Good”, “Good”]), there is however a probability that two different grades-border the middlemost of this dilated scale (here: [“Acceptable”, “Good”]).+border the middlemost of this dilated scale (here: “Acceptable” and “Good”). But only the lower grade (here: “Acceptable”) rewards consensus, and thus is considered to be the most consensual. Indeed, if any other choice obtains less scattered judgments (eg. [“Acceptable”, “Acceptable”, “Acceptable”, “Acceptable”, “Good”, “Good”]) all enclosed into these two grades, it will obtain a most consensual majoritary grade-greater or egal to the one of this choice (here: “Acceptable”).+greater or egal (here: “Acceptable”) to the one of this choice. Which would not necessarily be the case with the greater grade (here: “Good”).  ## Ranking many choices
hjugement.cabal view
@@ -1,7 +1,10 @@-author: Julien Moutinho <julm+hjugement@autogeree.net>-build-type: Simple-cabal-version: >= 1.18-category: Language+name: hjugement+-- PVP:  +-+------- breaking API changes+--       | | +----- non-breaking API additions+--       | | | +--- code changes with no API change+version: 2.0.0.20180903+category: Politic+synopsis: Majority Judgment. description:   A library for the <http://libgen.io/book/index.php?md5=BF67AA4298C1CE7633187546AA53E01D Majority Judgment>.   .@@ -11,10 +14,11 @@   in theory and in practice”.   .   For introductory explanations, you can read:-  the README.md (en) and/or-  Marjolaine Leray's comic: <https://www.lechoixcommun.fr/articles/Vous_reprendrez_bien_un_peu_de_democratie-2.html Vous reprendrez bien un peu de démocratie ?> (fr)   .-  Or watch: Rida Laraki's conference: <https://mixitconf.org/2017/majority-judgment Le Jugement Majoritaire> (fr)+  * the accompanying README.md file (en),+  * Marjolaine Leray's comic: <https://www.lechoixcommun.fr/articles/Vous_reprendrez_bien_un_peu_de_democratie-2.html Vous reprendrez bien un peu de démocratie ?> (fr),+  * the dedicated web sites: <https://mieuxvoter.fr> and <https://lechoixcommun.fr>,+  * or watch: Rida Laraki's conference: <https://mixitconf.org/2017/majority-judgment Le Jugement Majoritaire> (fr).   .   For comprehensive studies, you can read Michel Balinski and Rida Laraki's:   .@@ -22,51 +26,77 @@   * cahier: <http://www.lamsade.dauphine.fr/sites/default/IMG/pdf/cahier_377.pdf Majority Judgment vs. Majority Rule> (en)   * paper: <https://1007421605497013616-a-1802744773732722657-s-sites.googlegroups.com/site/ridalaraki/xfiles/BalinskiLarakiJudgeDontVotecahierderecherche2010-27.pdf Judge : Don't Vote!> (en)   * article: <https://www.cairn.info/revue-francaise-d-economie-2012-4-page-11.htm Jugement majoritaire versus vote majoritaire (via les présidentielles 2011-2012)> (fr)-extra-source-files: extra-doc-files: README.md-extra-tmp-files: license: GPL-3 license-file: COPYING-maintainer: Julien Moutinho <julm+hjugement@autogeree.net>-name: hjugement stability: experimental-synopsis: Majority Judgment.-tested-with: GHC==8.0.2-version: 1.0.0.20170808+author:      Julien Moutinho <julm+hjugement@autogeree.net>+maintainer:  Julien Moutinho <julm+hjugement@autogeree.net>+bug-reports: Julien Moutinho <julm+hjugement@autogeree.net>+-- homepage: +build-type: Simple+cabal-version: 1.24+tested-with: GHC==8.4.3+extra-source-files:+  stack.yaml+extra-tmp-files:+ Source-Repository head  location: git://git.autogeree.net/hjugement  type:     git  Library   exposed-modules:-    Hjugement-    Hjugement.Majority+    Majority.Gauge+    Majority.Judgment+    Majority.Merit+    Majority.Section+    Majority.Value   default-language: Haskell2010   default-extensions:-  ghc-options: -Wall -fno-warn-tabs+    NoImplicitPrelude+    NamedFieldPuns+  ghc-options:+    -Wall+    -Wincomplete-uni-patterns+    -Wincomplete-record-updates+    -fno-warn-tabs+    -- -fhide-source-paths   build-depends:-    base >= 4.6 && < 5-    , containers > 0.5+      base >= 4.6 && < 5+    , containers >= 0.5+    , hashable >= 1.2.6+    -- , transformers >= 0.5.2+    , unordered-containers >= 0.2.8  Test-Suite hjugement-test   type: exitcode-stdio-1.0-  default-language: Haskell2010-  default-extensions:-  ghc-options: -Wall -fno-warn-tabs   hs-source-dirs: test   main-is: Main.hs   other-modules:     HUnit     QuickCheck     Types+  default-language: Haskell2010+  default-extensions:+    ImplicitPrelude+  ghc-options:+    -Wall+    -Wincomplete-uni-patterns+    -Wincomplete-record-updates+    -fno-warn-tabs+    -- -fhide-source-paths   build-depends:-    base >= 4.6 && < 5-    , containers >= 0.5 && < 0.6-    , hjugement+      hjugement+    , base >= 4.6 && < 5+    , containers >= 0.5+    , hashable >= 1.2.6     , QuickCheck >= 2.0+    , random >= 1.1     , tasty >= 0.11-    , tasty-hunit+    , tasty-hunit >= 0.9     , tasty-quickcheck-    , text-    , transformers >= 0.4 && < 0.6+    , text >= 1.2+    , transformers >= 0.5+    , unordered-containers >= 0.2.8
+ stack.yaml view
@@ -0,0 +1,3 @@+resolver: lts-12.8+packages:+- '.'
test/HUnit.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE OverloadedLists #-}+{-# LANGUAGE TypeFamilies #-} {-# OPTIONS_GHC -fno-warn-orphans #-} module HUnit where @@ -6,175 +7,725 @@ import Test.Tasty.HUnit  import Control.Arrow (second)-import qualified Data.Map.Strict as Map+import Data.Hashable (Hashable)+import Data.Ratio ((%))+import Data.Tree (Tree(..))+import GHC.Exts (IsList(..))+import Prelude+import qualified Data.HashMap.Strict as HM -import Hjugement+import Majority.Judgment import Types  hunits :: TestTree hunits = 	testGroup "HUnit"-	 [ testGroup "Value" $+	 [ testGroup "MajorityValue" $ 		 [ testCompareValue-			 [(3,15), (2,7), (1,3), (0::Int,2)]-			 [(3,16), (2,6), (1,2), (0,3)]-		 , testGroup "OfMerits"+			 (majorityValue $ Merit [(3,15), (2,7), (1,3), (0::Int,2)])+			 (majorityValue $ Merit [(3,16), (2,6), (1,2), (0,3)])+		 , testGroup "Merit" 			 [  let m = mkMerit ['A'..'F'] in-				testValueOfMerits+				testMajorityValueOfMerits 				 [ (The, m [136,307,251,148,84,74]) 				 ]-				 [ (The, [('C',251),('B',307),('D',148),('E',84),('A',136),('F',74)])+				 [ (The,+					 [ Middle ( 57 % 1) 'C' 'C'+					 , Middle (137 % 1) 'B' 'C'+					 , Middle (148 % 1) 'B' 'D'+					 , Middle ( 22 % 1) 'B' 'E'+					 , Middle ( 62 % 1) 'A' 'E'+					 , Middle ( 74 % 1) 'A' 'F'+					 ]) 				 ] 			 , let m = mkMerit [ToReject .. TooGood] in-				testValueOfMerits+				testMajorityValueOfMerits 				 [ (This, m [12,10,21,5,5,5,2]) 				 , (That, m [12,16,22,3,3,3,1]) 				 ]-				 [ (This, [(Acceptable,21),(Insufficient,10),(Good,5),(ToReject,12),(Perfect,5),(VeryGood,5),(TooGood,2)])-				 , (That, [(Acceptable,22),(Insufficient,16),(ToReject,12),(VeryGood,3),(Perfect,3),(Good,3),(TooGood,1)])+				 [ (This, [ Middle (8 % 1) Acceptable   Acceptable+				          , Middle (5 % 1) Insufficient Acceptable+				          , Middle (5 % 1) Insufficient Good+				          , Middle (5 % 1) ToReject     VeryGood+				          , Middle (5 % 1) ToReject     Perfect+				          , Middle (2 % 1) ToReject     TooGood+				          ])+				 , (That, [ Middle ( 2 % 1) Acceptable   Acceptable+				          , Middle (16 % 1) Insufficient Acceptable+				          , Middle ( 2 % 1) ToReject     Acceptable+				          , Middle ( 3 % 1) ToReject     Good+				          , Middle ( 3 % 1) ToReject     VeryGood+				          , Middle ( 3 % 1) ToReject     Perfect+				          , Middle ( 1 % 1) ToReject     TooGood+				          ]) 				 ] 			 ]-		 , testGroup "OfOpinions"-			 [ testValueOfOpinions [No,Yes]-				   [The]-				 [ [No ]-				 , [No ]-				 , [No ]-				 , [No ]-				 , [Yes]-				 , [Yes]-				 ]-				 [ (The, [(No,4),(Yes,2)])-				 ]-			 , testValueOfOpinions [No,Yes]-				   [The]-				 [ [No ]-				 , [No ]-				 , [No ]-				 , [Yes]-				 , [Yes]-				 , [Yes]-				 ]-				 [ (The, [(No,3),(Yes,3)])-				 ]-			 , testValueOfOpinions [No,Yes]-				   [This, That]-				 [ [No  , No ]-				 , [No  , Yes]-				 , [No  , Yes]-				 , [No  , Yes]-				 , [Yes , Yes]-				 , [Yes , Yes]+		 , testGroup "MajorityRanking"+			 [ testMajorityValueOfOpinions+				 [ (The, [No,No,No,No,Yes,Yes]) ]+				 [ (The, [ Middle (1 % 1) No No+				         , Middle (2 % 1) No Yes+				         ]) ]+			 , testMajorityValueOfOpinions+				 [ (The, [No,No,No,Yes,Yes,Yes]) ]+				 [ (The, [ Middle (3 % 1) No Yes ]) ]+			 , testMajorityValueOfOpinions+				 [ (The, [No,No,No,No,Yes,Yes,Yes]) ]+				 [ (The, [ Middle (1 % 2) No No+				         , Middle (3 % 1) No Yes ]) ]+			 , testMajorityValueOfOpinions+				 [ (This, [No,No,No,No,Yes,Yes])+				 , (That, [No,Yes,Yes,Yes,Yes,Yes]) 				 ]-				 [ (This, [(No,4),(Yes,2)])-				 , (That, [(Yes,5),(No,1)])+				 [ (This, [ Middle (1 % 1) No No+				          , Middle (2 % 1) No Yes+				          ])+				 , (That, [ Middle (2 % 1) Yes Yes+				          , Middle (1 % 1) No  Yes+				          ]) 				 ]-			 , testValueOfOpinions [No,Yes]-				   [This, That]-				 [ [No  , No ]-				 , [No  , No ]-				 , [No  , No ]-				 , [No  , Yes]-				 , [No  , Yes]-				 , [No  , Yes]+			 , testMajorityValueOfOpinions+				 [ (This, [No,No,No,No,No,No])+				 , (That, [No,No,No,Yes,Yes,Yes]) 				 ]-				 [ (This, [(No,6),(Yes,0)])-				 , (That, [(No,3),(Yes,3)])+				 [ (This, [Middle (3 % 1) No No])+				 , (That, [Middle (3 % 1) No Yes]) 				 ]-			 , testValueOfOpinions [No,Yes]-				   [This, That]-				 [ [Yes , No ]-				 , [Yes , No ]-				 , [Yes , No ]-				 , [Yes , Yes]-				 , [Yes , Yes]-				 , [Yes , Yes]+			 , testMajorityValueOfOpinions+				 [ (This, [Yes,Yes,Yes,Yes,Yes,Yes])+				 , (That, [No,No,No,Yes,Yes,Yes]) 				 ]-				 [ (This, [(Yes,6),(No,0)])-				 , (That, [(No,3),(Yes,3)])+				 [ (This, [Middle (3 % 1) Yes Yes])+				 , (That, [Middle (3 % 1) No  Yes]) 				 ]-			 , testValueOfOpinions [No,Yes]-				   [This, That]-				 [ [No  , No ]-				 , [No  , No ]-				 , [Yes , No ]-				 , [Yes , Yes]-				 , [Yes , Yes]-				 , [Yes , Yes]+			 , testMajorityValueOfOpinions+				 [ (This, [No,No,Yes,Yes,Yes,Yes])+				 , (That, [No,No,No,Yes,Yes,Yes]) 				 ]-				 [ (This, [(Yes,4),(No,2)])-				 , (That, [(No,3),(Yes,3)])+				 [ (This, [ Middle (1 % 1) Yes Yes+				          , Middle (2 % 1) No  Yes+				          ])+				 , (That, [ Middle (3 % 1) No Yes ]) 				 ]-			 , testValueOfOpinions [ToReject,Insufficient,Acceptable,Good,VeryGood,Perfect]-				   [1::Int ..6]-				 [ [Perfect,Perfect,Acceptable,VeryGood,Good,VeryGood]-				 , [Perfect,VeryGood,Perfect,Good,Acceptable,Acceptable]-				 , [VeryGood,VeryGood,Good,Acceptable,VeryGood,Insufficient]-				 , [Perfect,VeryGood,VeryGood,Good,Good,Acceptable]-				 , [Perfect,Good,VeryGood,Good,Good,Acceptable]-				 , [Perfect,VeryGood,Perfect,Good,Good,Good]+			 , testMajorityValueOfOpinions+				 [ (1::Int, [Perfect,Perfect,VeryGood,Perfect,Perfect,Perfect])+				 , (2, [Perfect,VeryGood,VeryGood,VeryGood,Good,VeryGood])+				 , (3, [Acceptable,Perfect,Good,VeryGood,VeryGood,Perfect])+				 , (4, [VeryGood,Good,Acceptable,Good,Good,Good])+				 , (5, [Good,Acceptable,VeryGood,Good,Good,Good])+				 , (6, [VeryGood,Acceptable,Insufficient,Acceptable,Acceptable,Good]) 				 ]-				 [ (1, [(Perfect,5),(VeryGood,1),(ToReject,0),(Insufficient,0),(Acceptable,0),(Good,0)])-				 , (2, [(VeryGood,4),(Good,1),(Perfect,1),(ToReject,0),(Insufficient,0),(Acceptable,0)])-				 , (3, [(VeryGood,2),(Good,1),(Perfect,2),(Acceptable,1),(ToReject,0),(Insufficient,0)])-				 , (4, [(Good,4),(Acceptable,1),(VeryGood,1),(ToReject,0),(Insufficient,0),(Perfect,0)])-				 , (5, [(Good,4),(Acceptable,1),(VeryGood,1),(ToReject,0),(Insufficient,0),(Perfect,0)])-				 , (6, [(Acceptable,3),(Good,1),(Insufficient,1),(VeryGood,1),(ToReject,0),(Perfect,0)])+				 [ (1, [ Middle (2 % 1) Perfect      Perfect+				       , Middle (1 % 1) VeryGood     Perfect+				       ])+				 , (2, [ Middle (2 % 1) VeryGood     VeryGood+				       , Middle (1 % 1) Good         Perfect+				       ])+				 , (3, [ Middle (1 % 1) VeryGood     VeryGood+				       , Middle (1 % 1) Good         Perfect+				       , Middle (1 % 1) Acceptable   Perfect+				       ])+				 , (4, [ Middle (2 % 1) Good         Good+				       , Middle (1 % 1) Acceptable   VeryGood+				       ])+				 , (5, [ Middle (2 % 1) Good         Good+				       , Middle (1 % 1) Acceptable   VeryGood+				       ])+				 , (6, [ Middle (1 % 1) Acceptable   Acceptable+				       , Middle (1 % 1) Acceptable   Good+				       , Middle (1 % 1) Insufficient VeryGood+				       ]) 				 ] 			 ]+		 , testGroup "Section"+			 [ testSection "0 judge"+				 ([]::Choices C2)+				 ([]::Judges Int G6)+				 (node0 [])+				 (Right $ node0 [])+			 , testSection "1 judge, default grade"+				 [This]+				 [(1::Int,ToReject)]+				 (node0 [])+				 (Right $ node0 [(This, [(1,[(ToReject,1%1)])])])+			 , testSection "1 judge, default grade, 2 choices"+				 [This, That]+				 [(1::Int,ToReject)]+				 (node0 [])+				 (Right $ node0 [ (This, [(1,[(ToReject,1%1)])])+				                , (That, [(1,[(ToReject,1%1)])])+				                ])+			 , testSection "1 judge, default grade"+				 [This]+				 [(1::Int,ToReject)]+				 (node0 [(This,[(1,Section Nothing Nothing)])])+				 (Right $ node0 [(This,[(1,[(ToReject,1%1)])])])+			 , testSection "2 judges, default grade"+				 [This]+				 [(1::Int,ToReject), (2::Int,ToReject)]+				 (node0+					 [ (This, [(1,Section Nothing Nothing)])+					 ])+				 (Right $ node0+					 [ (This, [ (1,[(ToReject,1%1)])+					          , (2,[(ToReject,1%1)])+					          ])+					 ])+			 , testSection "ErrorSection_unknown_choices"+				 []+				 [(1::Int,ToReject)]+				 (node0 [(This,[])])+				 (Left $ ErrorSection_unknown_choices [This])+			 , testSection "ErrorSection_unknown_choices"+				 []+				 [(1::Int,ToReject)]+				 (node0 [(This,[(2,Section Nothing Nothing)])])+				 (Left $ ErrorSection_unknown_choices [This])+			 , testSection "ErrorSection_unknown_choices"+				 [This]+				 [(1::Int,ToReject)]+				 (node0 [ (This,[(1,Section Nothing Nothing)])+				        , (That,[(2,Section Nothing Nothing)])+				        ])+				 (Left $ ErrorSection_unknown_choices [That])+			 , testSection "ErrorSection_unknown_judges"+				 [This]+				 [(1::Int,ToReject)]+				 (node0 [(This,[(2,Section Nothing Nothing)])])+				 (Left $ ErrorSection_unknown_judges [(This,[2])])+			 , testSection "1 judge, 1 grade"+				 [This]+				 [(1::Int,ToReject)]+				 (node0 [(This,[(1,Section Nothing (Just Acceptable))])])+				 (Right $ node0 [(This,[(1,[(Acceptable,1%1)])])])+			 , testSection "1 judge, 1 grade, 2 sections"+				 [This]+				 [(1::Int,ToReject)]+				 (Node+					 [ (This, [(1,Section Nothing (Just Acceptable))]) ]+					 [ node0 [(This, [(1,Section (Just $ 1%2) Nothing)])]+					 , node0 [(This, [(1,Section (Just $ 1%2) Nothing)])]+					 ])+				 (Right $ Node+					 [ (This, [(1,[(Acceptable,1%1)])]) ]+					 [ node0 [(This, [(1,[(Acceptable,1%1)])])]+					 , node0 [(This, [(1,[(Acceptable,1%1)])])]+					 ])+			 , testSection "sectionNodeShare with judge"+				 [This]+				 [(1::Int,ToReject), (2,Insufficient)]+				 (Node+					 [(This, [(1,Section Nothing (Just Acceptable))])]+					 [ node0 $ SectionNode (Just $ 1%3) [(This, [ (1,Section (Just $ 1%2) Nothing)+					                                            , (2,Section Nothing Nothing)+					                                            ])]+					 , node0                            [(This, [ (1,Section (Just $ 1%2) Nothing)+					                                            , (2,Section Nothing (Just Good))+					                                            ])]+					 ])+				 (Right $ Node+					 [ (This, [ (1,[(Acceptable,1%2 + 1%2)])+					          , (2,[(Insufficient,1%3), (Good,2%3)])+					          ]) ]+					 [ node0 [(This, [ (1,[(Acceptable,1%1)])+					                 , (2,[(Insufficient,1%1)])+					                 ])]+					 , node0 [(This, [ (1,[(Acceptable,1%1)])+					                 , (2,[(Good,1%1)])+					                 ])]+					 ])+			 , testSection "sectionNodeShare without judge"+				 [This]+				 [(1::Int,ToReject), (2,Insufficient)]+				 (Node+					 [(This, [(1,Section Nothing (Just Acceptable))])]+					 [ node0 $ SectionNode (Just $ 1%3) [(This, [ (1,Section (Just $ 1%2) Nothing) ])]+					 , node0                            [(This, [ (1,Section (Just $ 1%2) Nothing)+					                                            , (2,Section Nothing (Just Good))+					                                            ])]+					 ])+				 (Right $ Node+					 [ (This, [ (1,[(Acceptable,1%2 + 1%2)])+					          , (2,[(Insufficient,1%3), (Good,2%3)])+					          ]) ]+					 [ node0 [(This, [ (1,[(Acceptable,1%1)])+					                 , (2,[(Insufficient,1%1)])+					                 ])]+					 , node0 [(This, [ (1,[(Acceptable,1%1)])+					                 , (2,[(Good,1%1)])+					                 ])]+					 ])+			 , testSection "1 judge, 2 grades, 2 sections"+				 [This]+				 [(1::Int,ToReject)]+				 (Node+					 [ (This, [(1,Section Nothing (Just Acceptable))]) ]+					 [ node0 [(This, [(1,Section (Just $ 1%2) Nothing)])]+					 , node0 [(This, [(1,Section (Just $ 1%2) (Just Good))])]+					 ])+				 (Right $ Node+					 [(This, [(1,[(Acceptable,1%2), (Good,1%2)])])]+					 [ node0 [(This, [(1,[(Acceptable,1%1)])])]+					 , node0 [(This, [(1,[(Good,1%1)])])]+					 ])+			 , testSection "1 judge, 2 grades, 2 sections (1 default)"+				 [This]+				 [(1::Int,ToReject)]+				 (Node+					 [ (This, [(1,Section Nothing (Just Acceptable))]) ]+					 [ node0 [(This, [(1,Section Nothing Nothing)])]+					 , node0 [(This, [(1,Section (Just $ 1%2) (Just Good))])]+					 ])+				 (Right $ Node+					 [(This, [(1,[(Acceptable,1%2), (Good,1%2)])])]+					 [ node0 [(This, [(1,[(Acceptable,1%1)])])]+					 , node0 [(This, [(1,[(Good,1%1)])])]+					 ])+			 , testSection "1 judge, 3 grades, 3 sections (2 default)"+				 [This]+				 [(1::Int,ToReject)]+				 (Node+					 [ (This, [(1,Section Nothing (Just Acceptable))]) ]+					 [ node0 [(This, [(1,Section Nothing Nothing)])]+					 , node0 [(This, [(1,Section (Just $ 1%2) (Just Good))])]+					 , node0 [(This, [(1,Section Nothing (Just VeryGood))])]+					 ])+				 (Right $ Node+					 [(This, [(1,[(Acceptable,1%4), (Good,1%2), (VeryGood,1%4)])])]+					 [ node0 [(This, [(1,[(Acceptable,1%1)])])]+					 , node0 [(This, [(1,[(Good,1%1)])])]+					 , node0 [(This, [(1,[(VeryGood,1%1)])])]+					 ])+			 , testSection "ErrorSection_invalid_shares sum not 1"+				 [This]+				 [(1::Int,ToReject)]+				 (Node+					 [ (This, [(1,Section Nothing (Just Acceptable))]) ]+					 [ node0 [(This, [(1,Section (Just $ 1%2) Nothing)])]+					 , node0 [(This, [(1,Section (Just $ 1%3) (Just Good))])]+					 ])+				 (Left $ ErrorSection_invalid_shares [(This, [(1,[1%2,1%3])])])+			 , testSection "ErrorSection_invalid_shares negative share"+				 [This]+				 [(1::Int,ToReject)]+				 (Node+					 [ (This, [(1,Section Nothing (Just Acceptable))]) ]+					 [ node0 [(This, [(1,Section (Just $ 1%2) Nothing)])]+					 , node0 [(This, [(1,Section (Just $ -1%2) (Just Good))])]+					 ])+				 (Left $ ErrorSection_invalid_shares [(This, [(1,[1%2,-1%2])])])+			 , testSection "2 judges, 3 grade, 3 sections (1 default)"+				 [This]+				 [(1::Int,ToReject), (2::Int,ToReject)]+				 (Node+					 [ (This, [(1,Section Nothing (Just Acceptable))])+					 ]+					 [ node0+						 [ (This, [(1,Section Nothing Nothing)])+						 ]+					 , node0+						 [ (This, [(1,Section (Just $ 1%2) (Just Good))])+						 ]+					 ])+				 (Right $ Node+					 [ (This, [ (1,[(Acceptable,1%2), (Good,1%2)])+					          , (2,[(ToReject,1%1)])+					          ])+					 ]+					 [ node0+						 [ (This, [ (1,[(Acceptable,1%1)])+						          , (2,[(ToReject,1%1)])+						          ])+						 ]+					 , node0+						 [ (This, [ (1,[(Good,1%1)])+						          , (2,[(ToReject,1%1)])+						          ])+						 ]+					 ])+			 , testSection "2 judges, 4 grades, 5 sections (2 defaults)"+				 [This]+				 [(1::Int,ToReject), (2::Int,ToReject)]+				 (Node+					 [ (This, [(1,Section Nothing (Just Acceptable))])+					 ]+					 [ node0+						 [ (This, [(1,Section Nothing Nothing)])+						 ]+					 , node0+						 [ (This, [(1,Section (Just $ 1%2) (Just Good))])+						 ]+					 , Node+						 [ (This, [(1,Section Nothing (Just Good))])+						 ]+						 [ node0+							 [ (This, [ (1,Section Nothing (Just VeryGood))+							          , (2,Section Nothing (Just Insufficient))+							          ])+							 ]+						 ]+					 ])+				 (Right $ Node+					 [ (This, [ (1,[(Acceptable,1%4), (Good,1%2), (VeryGood,1%4)])+					          , (2,[(ToReject,2%3), (Insufficient,1%3)])+					          ])+					 ]+					 [ node0+						 [ (This, [ (1,[(Acceptable,1%1)])+						          , (2,[(ToReject,1%1)])+						          ])+						 ]+					 , node0+						 [ (This, [ (1,[(Good,1%1)])+						          , (2,[(ToReject,1%1)])+						          ])+						 ]+					 , Node+						 [ (This, [ (1,[(VeryGood,1%1)])+						          , (2,[(Insufficient,1%1)])+						          ])+						 ]+						 [ node0+							 [ (This, [ (1,[(VeryGood,1%1)])+							          , (2,[(Insufficient,1%1)])+							          ])+							 ]+						 ]+					 ])+			 , testSection "1 judge, default grade, 2 choices"+				 [This, That]+				 [(1::Int,ToReject)]+				 (node0 [])+				 (Right $ node0 [ (This,[(1,[(ToReject,1%1)])])+				                , (That,[(1,[(ToReject,1%1)])])+				                ])+			 , testSection "2 judges, 2 choices"+				 [This, That]+				 [(1::Int,ToReject), (2::Int,ToReject)]+				 (Node+					 [ ]+					 [ node0+						 [ (This, [(1,Section Nothing (Just Good))])+						 , (That, [(2,Section Nothing (Just Insufficient))])+						 ]+					 , node0+						 [ (This, [(1,Section Nothing (Just Acceptable))])+						 , (That, [(2,Section Nothing (Just VeryGood))])+						 ]+					 ])+				 (Right $ Node+					 [ (This, [ (1,[(Good,1%2), (Acceptable,1%2)])+					          , (2,[(ToReject,1%1)])+					          ])+					 , (That, [ (1,[(ToReject,1%1)])+					          , (2,[(Insufficient,1%2), (VeryGood,1%2)])+					          ])+					 ]+					 [ node0 [ (This, [ (1,[(Good,1%1)])+					                  , (2,[(ToReject,1%1)])+					                  ])+					         , (That, [ (1,[(ToReject,1%1)])+					                  , (2,[(Insufficient,1%1)])+					                  ])+					         ]+					 , node0 [ (This, [ (1,[(Acceptable,1%1)])+					                  , (2,[(ToReject,1%1)])+					                  ])+					         , (That, [ (1,[(ToReject,1%1)])+					                  , (2,[(VeryGood,1%1)])+					                  ])+					         ]+					 ])+			 , testSection "1 judge, 1 choice"+				 [This]+				 [(1::Int,ToReject)]+				 (Node []+					 [ node0 [ (This, [(1,Section (Just $ 1%8) (Just Acceptable))])+					         ]+					 , node0 [ (This, [(1,Section Nothing Nothing)])+					         ]+					 ])+				 (Right $ Node+					 [ (This, [(1,[(Acceptable,1%8), (ToReject,7%8)])])+					 ]+					 [ node0 [ (This, [(1,[(Acceptable, 1%1)])])+					         ]+					 , node0 [ (This, [(1,[(ToReject, 1%1)])])+					         ]+					 ])+			 , testSection "1 judge, 1 choice (missing judge)"+				 [This]+				 [(1::Int,ToReject)]+				 (Node []+					 [ node0 [ (This, [(1,Section (Just $ 1%8) (Just Acceptable))])+					         ]+					 , node0 [ (This, [])+					         ]+					 ])+				 (Right $ Node+					 [ (This, [(1,[(Acceptable,1%8), (ToReject,7%8)])])+					 ]+					 [ node0 [ (This, [(1,[(Acceptable, 1%1)])])+					         ]+					 , node0 [ (This, [(1,[(ToReject, 1%1)])])+					         ]+					 ])+			 , testSection "1 judge, 1 choice (missing judge)"+				 [This]+				 [(1::Int,ToReject)]+				 (Node []+					 [ node0 [ (This, [])+					         ]+					 , node0 [ (This, [(1,Section (Just $ 1%8) (Just Acceptable))])+					         ]+					 ])+				 (Right $ Node+					 [ (This, [(1,[(Acceptable,1%8), (ToReject,7%8)])])+					 ]+					 [ node0 [ (This, [(1,[(ToReject, 1%1)])])+					         ]+					 , node0 [ (This, [(1,[(Acceptable, 1%1)])])+					         ]+					 ])+			 , testSection "1 judge, 1 choice (missing choice)"+				 [This]+				 [(1::Int,ToReject)]+				 (Node []+					 [ node0 [ (This, [(1,Section (Just $ 1%8) (Just Acceptable))])+					         ]+					 , node0 [+					         ]+					 ])+				 (Right $ Node+					 [ (This, [(1,[(Acceptable,1%8), (ToReject,7%8)])])+					 ]+					 [ node0 [ (This, [(1,[(Acceptable, 1%1)])])+					         ]+					 , node0 [ (This, [(1,[(ToReject, 1%1)])])+					         ]+					 ])+			 , testSection "1 judge, 1 choice (missing choice)"+				 [This]+				 [(1::Int,ToReject)]+				 (Node []+					 [ node0 [ +					         ]+					 , node0 [ (This, [(1,Section (Just $ 1%8) (Just Acceptable))])+					         ]+					 ])+				 (Right $ Node+					 [ (This, [(1,[(Acceptable,1%8), (ToReject,7%8)])])+					 ]+					 [ node0 [ (This, [(1,[(ToReject, 1%1)])])+					         ]+					 , node0 [ (This, [(1,[(Acceptable, 1%1)])])+					         ]+					 ])+			 , testSection "2 judges, 2 choices"+				 [This, That]+				 [(1::Int,ToReject), (2::Int,ToReject)]+				 (node0+					 [ (This, [(1,Section Nothing (Just Acceptable))])+					 , (That, [(2,Section (Just $ 1%8) (Just VeryGood))])+					 ])+				 (Right $ node0+					 [ (This, [ (1,[(Acceptable,1%1)])+					          , (2,[(ToReject,1%1)])+					          ])+					 , (That, [ (1,[(ToReject,1%1)])+					          , (2,[(VeryGood,1%1)])+					          ])+					 ])+			 , testSection "2 judges, 2 choices"+				 [This, That]+				 [(1::Int,ToReject), (2::Int,ToReject)]+				 (Node+					 [ ]+					 [ node0+						 [ (This, [(1,Section Nothing (Just Good))])+						 , (That, [(2,Section Nothing (Just Insufficient))])+						 ]+					 , node0+						 [ (This, [(1,Section Nothing (Just Acceptable))])+						 , (That, [(2,Section (Just $ 1%8) (Just VeryGood))])+						 ]+					 ])+				 (Right $ Node+					 [ (This, [ (1,[(Good,1%2), (Acceptable,1%2)])+					          , (2,[(ToReject,1%1)])+					          ])+					 , (That, [ (1,[(ToReject,1%1)])+					          , (2,[(Insufficient,7%8), (VeryGood,1%8)])+					          ])+					 ]+					 [ node0 [ (This, [ (1,[(Good,1%1)])+					                  , (2,[(ToReject,1%1)])+					                  ])+					         , (That, [ (1,[(ToReject,1%1)])+					                  , (2,[(Insufficient,1%1)])+					                  ])+					         ]+					 , node0 [ (This, [ (1,[(Acceptable,1%1)])+					                  , (2,[(ToReject,1%1)])+					                  ])+					         , (That, [ (1,[(ToReject,1%1)])+					                  , (2,[(VeryGood,1%1)])+					                  ])+					         ]+					 ])+			 , testSection "2 judges, 2 choices"+				 [This, That]+				 [(1::Int,ToReject), (2::Int,ToReject)]+				 (Node [ (This, [(1,Section Nothing (Just Acceptable))])+				       ]+				       [ node0 [ (This, [(1,Section Nothing Nothing)])+				               ]+				       , node0 [ (This, [ (1,Section (Just $ 1%2) (Just Good)) ])+				               , (That, [ (1,Section (Just $ 1%3) Nothing)+				                        , (2,Section (Just $ 1%5) (Just Insufficient))+				                        ])+				               ]+				       , Node [ (This, [(1,Section Nothing (Just Good))])+				              , (That, [(2,Section Nothing (Just VeryGood))])+				              ]+				              [ node0 [ (This, [ (1,Section Nothing (Just VeryGood))+				                               , (2,Section Nothing (Just Insufficient))+				                               ])+				                      , (That, [ (1,Section Nothing (Just Acceptable)) ])+				                      ]+				              , node0 [ (This, [ (1,Section Nothing (Just Acceptable))+				                               ])+				                      , (That, [ (1,Section Nothing (Just VeryGood))+				                               , (2,Section Nothing (Just Good))+				                               ])+				                      ]+				              ]+				       ])+				 (Right $+					Node [ (This, [ (1,[(Acceptable,1%4 + 1%8), (Good,1%2), (VeryGood,1%8)])+					              , (2,[(ToReject,1%3 + 1%3 + 1%6), (Insufficient,1%6)])+					              ])+					     , (That, [ (1,[(ToReject,1%3 + 1%3), (Acceptable,1%6), (VeryGood,1%6)])+					              , (2,[(ToReject,4%10), (Insufficient,1%5), (VeryGood,4%20), (Good,4%20)])+					              ])+					     ]+					     [ node0 [ (This, [ (1,[(Acceptable,1%1)]) -- 1%4+					                      , (2,[(ToReject,1%1)])   -- 1%3+					                      ])+					             , (That, [ (1,[(ToReject,1%1)])   -- 1%3+					                      , (2,[(ToReject,1%1)])   -- 4%10+					                      ])+					             ]+					     , node0 [ (This, [ (1,[(Good,1%1)])         -- 1%2+					                      , (2,[(ToReject,1%1)])     -- 1%3+					                      ])+					             , (That, [ (1,[(ToReject,1%1)])     -- 1%3+					                      , (2,[(Insufficient,1%1)]) -- 1%5+					                      ])+					             ]+					     , Node [ (This, [ (1,[(VeryGood,1%2), (Acceptable,1%2)])   -- 1%4+					                     , (2,[(Insufficient,1%2), (ToReject,1%2)]) -- 1%3+					                     ])+					            , (That, [ (1,[(Acceptable,1%2), (VeryGood,1%2)])   -- 1%3+					                     , (2,[(VeryGood,1%2), (Good,1%2)])         -- 4%10+					                     ])+					            ]+					            [ node0 [ (This, [ (1,[(VeryGood,1%1)])+					                             , (2,[(Insufficient,1%1)])+					                             ])+					                    , (That, [ (1,[(Acceptable,1%1)])+					                             , (2,[(VeryGood,1%1)])+					                             ])+					                    ]+					            , node0 [ (This, [ (1,[(Acceptable,1%1)])+					                             , (2,[(ToReject,1%1)])+					                             ])+					                    , (That, [ (1,[(VeryGood,1%1)])+					                             , (2,[(Good,1%1)])+					                             ])+					                    ]+					            ]+					     ]+				 )+			 ] 		 ] 	 ] - elide :: String -> String elide s | length s > 42 = take 42 s ++ ['…']         | otherwise = s -mkOpinion :: Ord prop =>-             Choices prop -> judge -> [grade] ->-             (judge, Opinion prop grade)-mkOpinion props j gs = (j, Map.fromList $ toList props `zip` gs)--mkMerit :: (Ord grade, Show grade) => [grade] -> [Count] -> Merit grade-mkMerit scal = fromList . (scal`zip`)--mkMerits :: (Ord prop, Ord grade) =>-            [grade] -> Choices prop -> [[grade]] ->-            Merits prop grade-mkMerits scal props opins =-	merits (Scale (fromList scal) (head scal)) props $ fromList $-	zipWith (mkOpinion props) [1::Int ..] opins+mkMerit :: (Ord grade, Show grade) => [grade] -> [Share] -> Merit grade+mkMerit gs = fromList . (gs`zip`) +mkMeritByChoice ::+ (Eq choice, Hashable choice, Ord grade) =>+ [(choice,[grade])] ->+ MeritByChoice choice grade+mkMeritByChoice os =+	meritByChoice $ fromList $+	second (fromList . zip [1::Int ..] . (singleGrade <$>)) <$> os  testCompareValue :: (Ord grade, Show grade) =>-                    [(grade, Count)] -> [(grade, Count)] -> TestTree+                    MajorityValue grade -> MajorityValue grade -> TestTree testCompareValue x y =-	testGroup (elide $ show (x,y))-	 [ testCase "x == x" $ Value x`compare`Value x @?= EQ-	 , testCase "y == y" $ Value y`compare`Value y @?= EQ-	 , testCase "x <  y" $ Value x`compare`Value y @?= LT-	 , testCase "y >  x" $ Value y`compare`Value x @?= GT+	testGroup (elide $ show (unMajorityValue x, unMajorityValue y))+	 [ testCase "x == x" $ x`compare`x @?= EQ+	 , testCase "y == y" $ y`compare`y @?= EQ+	 , testCase "x <  y" $ x`compare`y @?= LT+	 , testCase "y >  x" $ y`compare`x @?= GT 	 ] -testRanking :: (Ord prop, Ord grade, Show grade, Show prop) =>-               [grade] -> Choices prop -> [[grade]] ->-               Ranking prop -> TestTree-testRanking scal props opins expect =-	testCase (elide $ show (toList props,opins)) $-		majorityRanking (mkMerits scal props opins) @?= expect+testMajorityRanking ::+ (Eq choice, Hashable choice, Ord grade, Show grade, Show choice) =>+ [(choice, [grade])] ->+ MajorityRanking choice grade -> TestTree+testMajorityRanking os expect =+	testCase (elide $ show os) $+		majorityRanking (mkMeritByChoice os) @?= expect -testValueOfOpinions :: (Show grade, Show prop, Ord grade, Ord prop) =>-             [grade] -> Choices prop -> [[grade]] ->-             [(prop, [(grade,Count)])] -> TestTree-testValueOfOpinions scal props opins expect =-	testCase (elide $ show (toList props,opins)) $-		majorityValueByChoice (mkMerits scal props opins)-		 @?= ((Value`second`)<$>expect)+testMajorityValueOfOpinions ::+ (Show grade, Show choice, Ord grade, Eq choice, Hashable choice) =>+ [(choice, [grade])] ->+ [(choice, [Middle grade])] -> TestTree+testMajorityValueOfOpinions os expect =+	testCase (elide $ show os) $+		majorityValueByChoice (mkMeritByChoice os)+		 @?= (MajorityValue<$>HM.fromList expect) -testValueOfMerits :: (Show grade, Show prop, Ord grade, Ord prop) =>-             Merits prop grade ->-             [(prop, [(grade,Count)])] -> TestTree-testValueOfMerits ms expect =-	testCase (elide $ show ms) $+testMajorityValueOfMerits ::+ (Show grade, Show choice, Ord grade, Eq choice, Hashable choice) =>+ MeritByChoice choice grade ->+ [(choice, [Middle grade])] -> TestTree+testMajorityValueOfMerits ms expect =+	testCase (elide $ show $ unMeritByChoice ms) $ 		majorityValueByChoice ms-		 @?= ((Value`second`)<$>expect)+		 @?= (MajorityValue<$>HM.fromList expect)++testSection ::+ Eq choice =>+ Hashable choice =>+ Eq judge =>+ Hashable judge =>+ Ord grade =>+ Show choice =>+ Show judge =>+ Show grade =>+ String ->+ Choices choice ->+ Judges judge grade ->+ Tree (SectionNode choice judge grade) ->+ Either (ErrorSection choice judge grade)+        (Tree (OpinionsByChoice choice judge grade)) ->+ TestTree+testSection msg cs js ss expect =+	testCase (elide msg) $+		opinionsBySection cs js ss @?= expect++node0 :: a -> Tree a+node0 = (`Node`[])++instance (Eq choice, Hashable choice) => IsList (SectionNode choice judge grade) where+	type Item (SectionNode choice judge grade) = (choice, SectionByJudge judge grade)+	fromList = SectionNode Nothing . fromList+	toList = GHC.Exts.toList . sectionByJudgeByChoice
test/QuickCheck.hs view
@@ -7,21 +7,27 @@ import Test.Tasty import Test.Tasty.QuickCheck +import Control.Arrow (first) import Control.Monad (replicateM)+import Data.Hashable (Hashable)+import qualified Data.Map.Strict as Map+import Data.Ratio+import GHC.Exts (IsList(..))+import Prelude+import System.Random (Random(..)) import qualified Data.Set as Set -import Hjugement+import Majority.Judgment import Types  quickchecks :: TestTree quickchecks = 	testGroup "QuickCheck"-	 [ testProperty "arbitraryJudgments" $ \(SameLength (x::[(G6,Count)],y)) ->-		let (gx, cx) = unzip x in-		let (gy, cy) = unzip y in-		gx == gy && sum cx == sum cy-	 , testGroup "Value"-		 [ testProperty "compare" $ \(SameLength (x::Value G6,y)) ->+	 [ testProperty "arbitraryMerits" $ \(SameLength (Merit x::Merit G6,Merit y::Merit G6)) ->+		Map.keys x == Map.keys y &&+		sum x == sum y+	 , testGroup "MajorityValue"+		 [ testProperty "compare" $ \(SameLength (x::MajorityValue G6,y)) -> 			expandValue x`compare` expandValue y == x`compare`y 		 ] 	 {-@@ -35,24 +41,28 @@ 	 -} 	 ] --- | Decompress a 'Value'.-expandValue :: Value a -> [a]-expandValue (Value []) = []-expandValue (Value ((x,c):xs)) = replicate c x ++ expandValue (Value xs)+-- | Decompress a 'MajorityValue'.+expandValue :: MajorityValue a -> [a]+expandValue (MajorityValue ms) = +	let d = foldr lcm 1 (denominator . middleShare <$> ms) in+	go $ (\m -> (numerator (middleShare m) * d, lowGrade m, highGrade m)) <$> ms+	where+	go [] = []+	go ((s,l,h):xs) = concat (replicate (fromIntegral s) [l, h]) ++ go xs --- | @arbitraryJudgments n@ arbitrarily generates 'n' lists of pairs of grade and 'Count'+-- | @arbitraryMerits n@ arbitrarily generates 'n' lists of 'Merit' -- for the same arbitrary grades,--- and with the same total 'Count' of individual judgments.-arbitraryJudgments :: forall g. (Bounded g, Enum g) => Int -> Gen [[(g, Count)]]-arbitraryJudgments n = sized $ \s -> do+-- and with the same total 'Share' of individual judgments.+arbitraryMerits :: forall g. (Bounded g, Enum g, Ord g) => Int -> Gen [Merit g]+arbitraryMerits n = sized $ \shareSum -> do 	minG <- choose (fromEnum(minBound::g), fromEnum(maxBound::g)) 	maxG <- choose (minG, fromEnum(maxBound::g)) 	let gs::[g] = toEnum minG`enumFromTo`toEnum maxG-	let lg = maxG - minG + 1+	let lenGrades = maxG - minG + 1 	replicateM n $ do-		cs  <- resize s $ arbitrarySizedNaturalSum lg-		cs' <- arbitraryPad (lg - length cs) (return 0) cs-		return $ zip gs cs'+		shares  <- resize shareSum $ arbitrarySizedPositiveRationalSum lenGrades+		shares' :: [Share] <- arbitraryPad (lenGrades - length shares) (return 0) shares+		return $ Merit $ fromList $ zip gs shares'  -- | @arbitrarySizedNaturalSum maxLen@ -- arbitrarily chooses a list of 'length' at most 'maxLen',@@ -68,11 +78,43 @@ 		d <- choose (0, tot) 		(d:) <$> go (len-1) (tot - d) +-- | @arbitrarySizedPositiveRationalSum maxLen@+-- arbitrarily chooses a list of 'length' at most 'maxLen',+-- containing positive 'Rational's summing up to 'sized'.+arbitrarySizedPositiveRationalSum :: Int -> Gen [Rational]+arbitrarySizedPositiveRationalSum maxLen = sized (go maxLen . fromIntegral)+	where+	go :: Int -> Rational -> Gen [Rational]+	go len tot | len <= 0 = return []+	           | len == 1 = return [tot]+	           | tot <= 0 = return [tot]+	go len tot = do+		d <- choose (0, tot)+		(d:) <$> go (len-1) (tot - d)++instance Random Rational where+	randomR (minR, maxR) g =+		if d - b == 0+		then first (% b) $ randomR (a, c) g+		else first (bd2ac . nat2bd) $ randomR (0, toInteger (maxBound::Int)) g+		where+		a = numerator   minR+		b = denominator minR+		c = numerator   maxR+		d = denominator maxR+		nat2bd x = ((d - b) % toInteger (maxBound::Int)) * (x%1) + (b%1)+		bd2ac x = alpha * x + beta+			where+			alpha = (c-a) % (d-b)+			beta = (a%1) - alpha * (b%1)+		+	random = randomR (toInteger (minBound::Int)%1, toInteger (maxBound::Int)%1)+ -- | @arbitraryPad n pad xs@ -- arbitrarily grows list 'xs' with 'pad' elements -- up to length 'n'.-arbitraryPad :: Int -> Gen a -> [a] -> Gen [a]-arbitraryPad n pad [] = replicateM n pad+arbitraryPad :: (Num i, Integral i) => i -> Gen a -> [a] -> Gen [a]+arbitraryPad n pad [] = replicateM (fromIntegral n) pad arbitraryPad n pad xs = do 	(r, xs') <- go n xs 	if r > 0@@ -97,38 +139,46 @@ instance Arbitrary G6 where 	arbitrary = arbitraryBoundedEnum instance (Arbitrary g, Bounded g, Enum g, Ord g, Show g) => Arbitrary (Merit g) where-	arbitrary = fromList . head <$> arbitraryJudgments 1+	arbitrary = head <$> arbitraryMerits 1 	shrink (Merit m) = Merit <$> shrink m instance- ( Arbitrary p, Bounded p, Enum p, Ord p, Show p+ ( Arbitrary c, Bounded c, Enum c, Eq c, Hashable c, Show c  , Arbitrary g, Bounded g, Enum g, Ord g, Show g- ) => Arbitrary (Merits p g) where+ ) => Arbitrary (MeritByChoice c g) where 	arbitrary = do-		minP <- choose (fromEnum(minBound::p), fromEnum(maxBound::p))-		maxP <- choose (minP, fromEnum(maxBound::p))+		minP <- choose (fromEnum(minBound::c), fromEnum(maxBound::c))+		maxP <- choose (minP, fromEnum(maxBound::c)) 		let ps = toEnum minP`enumFromTo`toEnum maxP-		let ms = (fromList <$>) <$> arbitraryJudgments (maxP - minP + 1)+		let ms = arbitraryMerits (maxP - minP + 1) 		fromList . zip ps <$> ms-instance (Bounded g, Eq g, Integral g, Arbitrary g) => Arbitrary (Value g) where-	arbitrary = head . (Value <$>) <$> arbitraryJudgments 1-	shrink (Value vs) = Value <$> shrink vs+instance (Bounded g, Eq g, Integral g, Arbitrary g) => Arbitrary (MajorityValue g) where+	arbitrary = head . (majorityValue <$>) <$> arbitraryMerits 1+	shrink (MajorityValue vs) = MajorityValue <$> shrink vs+instance (Bounded g, Enum g) => Arbitrary (Middle g) where+	arbitrary = do+		lowG  <- choose (fromEnum(minBound::g), fromEnum(maxBound::g))+		highG <- choose (lowG, fromEnum(maxBound::g))+		share <- choose (0, 1)+		return $ Middle share (toEnum lowG) (toEnum highG)  -- * Type 'SameLength' newtype SameLength a = SameLength a  deriving (Eq, Show) instance Functor SameLength where 	fmap f (SameLength x) = SameLength (f x)-instance (Arbitrary g, Bounded g, Enum g) => Arbitrary (SameLength (Value g, Value g)) where-	arbitrary = do-		SameLength (x,y) <- arbitrary-		return $ SameLength (Value x, Value y)-instance (Arbitrary g, Bounded g, Enum g, Ord g, Show g) => Arbitrary (SameLength (Merit g, Merit g)) where+instance (Arbitrary g, Bounded g, Enum g, Ord g) => Arbitrary (SameLength (MajorityValue g, MajorityValue g)) where 	arbitrary = do 		SameLength (x,y) <- arbitrary-		return $ SameLength (fromList x, fromList y)-instance (Arbitrary g, Bounded g, Enum g) => Arbitrary (SameLength ([(g,Count)], [(g,Count)])) where+		return $ SameLength (MajorityValue x, MajorityValue y)+instance (Arbitrary g, Bounded g, Enum g, Ord g) => Arbitrary (SameLength (Merit g, Merit g)) where 	arbitrary = do-		vs <- arbitraryJudgments 2+		vs <- arbitraryMerits 2 		case vs of 		 [x,y] -> return $ SameLength (x,y) 		 _ -> undefined+instance (Arbitrary g, Bounded g, Enum g, Ord g) => Arbitrary (SameLength ([Middle g], [Middle g])) where+	arbitrary = do+		SameLength (m0, m1) <- arbitrary+		return $ SameLength+		 ( unMajorityValue $ majorityValue m0+		 , unMajorityValue $ majorityValue m1 )
test/Types.hs view
@@ -1,13 +1,20 @@+{-# LANGUAGE DeriveGeneric #-} module Types where +import Data.Hashable+import GHC.Generics (Generic)+import Prelude+ data G2 = No | Yes  deriving (Eq, Ord, Show) -data P1 = The- deriving (Eq, Ord, Show)+data C1 = The+ deriving (Eq, Ord, Show, Generic)+instance Hashable C1 -data P2 = This | That- deriving (Eq, Ord, Show)+data C2 = This | That+ deriving (Eq, Ord, Show, Generic)+instance Hashable C2  data G6 = ToReject | Insufficient | Acceptable | Good | VeryGood | Perfect | TooGood  deriving (Bounded, Enum, Eq, Ord, Show)