The basic idea is that you write data declarations like $( gen [d| data Tank db where Tank :: PK Tank -> Named name Char23 -> Tank db data Fish db where Fish :: PK Fish -> Named name Char23 -> Fish db data Parent db where Parent :: PK Parent -> Named child (Foreign db Fish) -> Named parent (Foreign db Fish) -> Parent db |] ) Once you expand all the type synonyms, this is just, e.g.: data Tank db = Tank (PK Tank) String but the extra information ("23" and "name") is visible to TH functions. (You can imagine that db is just IO). This is isomorphic to defining the database tables tank, with columns tank_id (primary key) name (char(23)) fish, with columns fish_id (primary key) name (char(23)) parent, with columns parent_id (primary key) child (foreign key) parent (foreign key) The TH function gen would generate: * a function that runs the various CREATE TABLE ... commands on the database * Various functions and classes to operate on values of the datatype, e.g. class Child a b | a -> b where child :: a -> b instance MonadDatabase db => Child (Parent db) (db (Fish db)) where child (Parent (Foreign child_id) _) = ... lookup child in database and build Fish value class Save a where save :: MonadDatabase m => a -> m () instance Save a where save (Fish fish_id name) = ... save fish to database ... (update if fish_id is PK i, create new row if it's New)