Suppose that I have some simple algebraic data (essentially enums) and another type which has these enums as fields.
data Color = Red | Green | Blue deriving (Eq, Show, Enum, Ord)
data Width = Thin | Normal | Fat deriving (Eq, Show, Enum, Ord)
data Height = Short | Medium | Tall deriving (Eq, Show, Enum, Ord)
data Object = Object { color :: Colour
, width :: Width
, height :: Height } deriving (Show)
Given a list of objects, I want to test that the attributes are all distinct. For this I have following functions (using sort from Data.List)
allDifferent = comparePairwise . sort
where comparePairwise xs = and $ zipWith (/=) xs (drop 1 xs)
uniqueAttributes :: [Object] -> Bool
uniqueAttributes objects = all [ allDifferent $ map color objects
, allDifferent $ map width objects
, allDifferent $ map height objects ]
This works, but is rather dissatisfying because I had to type each field (color, width, height) manually. In my actual code, there are more fields! Is there a way of 'mapping' the function
\field -> allDifferent $ map field objects
over the fields of an algebraic datatype like Object? I want to treat Object as a list of its fields (something that would be easy in e.g. javascript), but these fields have different types...
For this very specific situation (checking a set of attributes that are simple sum types with 0-arity constructors), you can use the following construction using
Data.Datagenerics:The key here is the function
signaturewhich takes an object and generically across its immediate children calculates the constructor name of each child. So:If there are any fields other than these simple sum types, (like say an attribute of type
data Weight = Weight Intor if you added aname :: Stringfield toObject), then this will suddenly fail.(Edited to add:) Note that you can use
constrIndex . toConstrin place ofshow . toConstrto use anInt-valued constructor index (basically, the index starting with 1 of the constructor within thedatadefinition), if this feels less indirect. If theConstrreturned bytoConstrhad anOrdinstance, there would be no indirection at all, but unfortunately...