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...
Here is a solution using generics-sop:
This assumes that the type
Objectyou want to compare is a record type and requires that you make this type an instance of the classGeneric, which can be done using Template Haskell:Let's try to see what's going on here by looking at a concrete example:
The line
map (unZ . unSOP . from)converts eachObjectinto a heterogeneous list (called an n-ary product in the library):The
hunzipthen turns this list of products into a product where each element is a list:Now, we apply
allDifferentto each list in the product:The product is now in fact homogeneous, as every position contains a
Bool, sohcollapseturns it into a normal homogeneous list again:The last step just applies
andto it: