Nm has the right idea, provided that the relation should be a diagram of the relation, and not the relation itself. Here is an example of using Data.Typeable as a type class and TypeRep as a type representation. In addition, I want all types to be displayed as an example.
-- Use Data.Typeable as our class of types import Data.Typeable data Relation = Relation String [Attribute] data Attribute = forall a. (Typeable a, Show a) => Attribute String (Proxy a) [Assertion a] -- Requires RankNTypes data Proxy a = Proxy data Assertion a where LessThan :: Ord a => a -> Assertion a Element :: Eq a => [a] -> Assertion a -- Requires GADTs -- Relation deriving instance Show Relation attributes (Relation _ x) = x -- Attribute deriving instance Show Attribute -- Requires StandaloneDeriving name (Attribute x _ _) = x validator :: Attribute -> forall a. (Typeable a) => a -> Maybe Bool validator (Attribute _ proxy assertions) value = (cast value) >>= \x -> Just $ all ((flip implementation) x) assertions dataType :: Attribute -> TypeRep dataType (Attribute _ proxy _) = getType proxy -- Proxy instance (Typeable a) => Show (Proxy a) where show = show . getType getType :: (Typeable a) => Proxy a -> TypeRep getType (_ :: Proxy a) = typeOf (undefined :: a) -- Requires ScopedTypeVariables -- Assertion deriving instance (Show a) => Show (Assertion a) implementation :: Assertion a -> a -> Bool implementation (LessThan y) x = x < y implementation (Element y) x = any ((==) x) y
The example checks to see if a pair of values ββare valid for various attribute attributes.
-- Example explain :: (Typeable a, Show a) => a -> Attribute -> String explain value attribute = case validator attribute value of Nothing -> (show value) ++ " can't be a " ++ (name attribute) ++ " because it isn't a " ++ (show (dataType attribute)) Just False -> (show value) ++ " can't be a " ++ (name attribute) ++ " because it fails an assertion" Just True -> (show value) ++ " can be a " ++ (name attribute) main = do putStrLn $ show people sequence [ putStrLn (explain value attribute) | value <- [150 :: Int, 700], attribute <- attributes people ] sequence [ putStrLn (explain value attribute) | value <- ["Alice", "George"], attribute <- attributes people ] where people = Relation "People" [ Attribute "Name" (Proxy :: Proxy String) [Element ["Alice", "Bob", "Eve"] ], Attribute "Height" (Proxy :: Proxy Int) [LessThan 300] ]
Here's the output of the program:
Relationship "People" [Attribute "Name" [Char] [Element ["Alice", "Bob", "Eve"]], Attribute "Height" Int [LessThan 300]]
150 cannot be a name because it is not [Char]
150 may be tall
700 cannot be a name because it is not [Char]
700 cannot be tall because it is not consistent with the statement
"Alice" may be the name
"Alice" cannot be tall because it is not int
"George" cannot be a name because he does not fulfill the statement
George can't be tall because it's not int
You can create your own class using your own representation of a smaller set of types, such as your data Type , and providing a different casting method.