I'm working on some code that interfaces to a database schema that models a persistent graph. Before I go into the details of my specific question, I thought it might help to provide some motivation. My schema is around books, people and author roles. A book has many author roles, where each role has a person. However, instead of allowing direct UPDATE queries on book objects, you must create a new book, and make modifications to the new version.
Now, back to Haskell land. I am currently working with a few type classes, but importantly I have HasRoles
and Entity
:
class HasRoles a where
-- Get all roles for a specific 'a'
getRoles :: a -> IO [Role]
class Entity a where
-- Update an entity with a new entity. Return the new entity.
update :: a -> a -> IO a
Here comes my problem. When you are updating a book, you need to create a new book version but you also need to copy over the previous books roles (otherwise you lose data). The simplest way to do this is:
instance Entity Book where
update orig newV = insertVersion V >>= copyBookRoles orig
This is fine, but there's something that bugs me, and that's the lack of any guarantee of the invariant that if something is an Entity
and HasRoles
, then inserting a new version will copy over the existing roles. I have thought of 2 options:
Use More Types
One 'solution' is to introduce the RequiresMoreWork a b
. Going from the above, insertVersion
now returns a HasRoles w => RequiresMoreWork w Book
. update
wants a Book
, so to get out of the RequiresMoreWork
value, we could call workComplete :: RequiresMoreWork () Book -> Book
.
The real problem with this though, is that the most important piece of the puzzle is the type signature of insertVersion
. If this doesn't match the invariants (for example, it made no mention of needing HasRoles
) then it all falls apart again, and we're back to violating an invariant.
Prove it with QuickCheck
Moves the problem out of compile time, but at least we're still asserting the invariant. In this case, the invariant is something like: for all entities that are also instances of HasRoles
, inserting a new version of an existing value should have the same roles.
I'm a bit stumped on this. In Lisp I'd use method modifiers, in Perl I'd use roles, but is there anything I can use in Haskell?