Я работаю над кодом, который взаимодействует со схемой базы данных, моделирующей постоянный граф. Прежде чем я углублюсь в детали своего конкретного вопроса, я подумал, что это может помочь дать некоторую мотивацию. Моя схема основана на книгах, людях и авторских ролях. В книге много авторских ролей, в каждой из которых есть человек. Однако вместо того, чтобы разрешать прямые запросы UPDATE к объектам книги, вы должны создать новую книгу и внести изменения в новую версию.
А теперь вернемся в страну Haskell. В настоящее время я работаю с несколькими классами типов, но, что важно, у меня есть HasRoles
и 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
Вот и моя проблема. Когда вы обновляете книгу, вам необходимо создать новую версию книги, но вы также должны скопировать роли предыдущих книг (иначе вы потеряете данные). Самый простой способ сделать это:
instance Entity Book where
update orig newV = insertVersion V >>= copyBookRoles orig
Это нормально, но есть что-то, что меня беспокоит, а именно отсутствие какой-либо гарантии инварианта, что если что-то является Entity
и HasRoles
, то при вставке новой версии существующие роли будут скопированы. Я придумал 2 варианта:
Одно «решение» - ввести RequiresMoreWork a b
. Исходя из вышеизложенного, insertVersion
теперь возвращает HasRoles w => RequiresMoreWork w Book
. обновлению
требуется Книга
, поэтому, чтобы выйти из значения RequiresMoreWork
, мы могли бы вызвать workComplete :: RequiresMoreWork () Book -> Book
.
Однако настоящая проблема заключается в том, что наиболее важной частью головоломки является сигнатура типа insertVersion
. Если это не соответствует инвариантам (например, не упоминается о необходимости HasRoles
), тогда все снова разваливается, и мы возвращаемся к нарушению инварианта.
Убирает проблему из времени компиляции, но, по крайней мере, мы все еще утверждаем инвариант. В этом случае инвариант выглядит примерно так: для всех сущностей, которые также являются экземплярами HasRoles
, вставка новой версии существующего значения должна иметь те же роли.
Я немного озадачен это. В Lisp я бы использовал модификаторы методов, в Perl я бы использовал роли, но есть ли что-нибудь, что я могу использовать в Haskell?