Я работаю над кодом, который взаимодействует со схемой базы данных, которая моделирует постоянный граф. Прежде чем перейти к деталям моего конкретного вопроса, я подумал, что это может помочь обеспечить некоторую мотивацию. Моя схема вокруг книг, людей и ролей авторов. Книга имеет много авторских ролей, где в каждой роли есть человек. Однако вместо того, чтобы разрешать прямые запросы UPDATE для объектов книги, вы должны создать новую книгу и внести изменения в новую версию.
Теперь вернемся к земле Хаскелла. В настоящее время я работаю с несколькими типами классов, но, что важно, у меня есть 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
. update
хочет Book
, поэтому, чтобы получить значение RequiresMoreWork
, мы могли бы позвонить workComplete :: RequiresMoreWork () Book -> Book
.
Однако реальная проблема заключается в том, что наиболее важной частью головоломки является сигнатура типа insertVersion
. Если это не совпадает с инвариантами (например, в нем не упоминается необходимость HasRoles
), то все снова разваливается, и мы возвращаемся к нарушению инварианта.
Докажите это с помощью QuickCheck
Удаляет проблему из времени компиляции, но по крайней мере мы все еще утверждаем инвариант. В этом случае инвариант выглядит примерно так: для всех сущностей, которые также являются экземплярами HasRoles
, вставка новой версии существующего значения должна иметь одинаковые роли.
Я немного озадачен этим. В Лиспе я бы использовал модификаторы методов, в Perl я бы использовал роли, но есть ли что-нибудь, что я могу использовать в Haskell?