Библиотека ixset
поможет вам в этом. Это библиотека, поддерживающая реляционную часть acid-state
, которая также обрабатывает версионную сериализацию ваших данных и / или гарантии параллелизма, в случае необходимости.
Смысл ixset
в том, что он автоматически управляет «ключами» для ввода данных.
Для вашего примера можно создать отношения один-ко-многим для ваших типов данных, например:
data User =
User
{ name :: String
, birthDate :: Date
} deriving (Ord, Typeable)
data Message =
Message
{ user :: User
, timestamp :: Date
, content :: String
} deriving (Ord, Typeable)
instance Indexable Message where
empty = ixSet [ ixGen (Proxy :: Proxy User) ]
Затем вы можете найти сообщение определенного пользователя. Если вы создали IxSet
, как это:
user1 = User "John Doe" undefined
user2 = User "John Smith" undefined
messageSet =
foldr insert empty
[ Message user1 undefined "bla"
, Message user2 undefined "blu"
]
... затем вы можете найти сообщения по user1
с:
user1Messages = toList $ messageSet @= user1
Если вам нужно найти пользователя сообщения, просто используйте функцию user
как обычно. Это моделирует отношения один ко многим.
Теперь для отношений «многие ко многим» в такой ситуации:
data User =
User
{ name :: String
, birthDate :: Date
, messages :: [Message]
} deriving (Ord, Typeable)
data Message =
Message
{ users :: [User]
, timestamp :: Date
, content :: String
} deriving (Ord, Typeable)
... вы создаете индекс с ixFun
, который может использоваться со списками индексов. Вот так:
instance Indexable Message where
empty = ixSet [ ixFun users ]
instance Indexable User where
empty = ixSet [ ixFun messages ]
Чтобы найти все сообщения пользователя, вы все равно используете ту же функцию:
user1Messages = toList $ messageSet @= user1
Дополнительно, при условии, что у вас есть индекс пользователей:
userSet =
foldr insert empty
[ User "John Doe" undefined [ messageFoo, messageBar ]
, User "John Smith" undefined [ messageBar ]
]
... вы можете найти всех пользователей для сообщения:
messageFooUsers = toList $ userSet @= messageFoo
Если вы не хотите обновлять пользователей сообщения или сообщений пользователя при добавлении нового пользователя / сообщения, вам следует вместо этого создать промежуточный тип данных, который моделирует отношения между пользователями и сообщениями, просто как в SQL (и удалите поля users
и messages
):
data UserMessage = UserMessage { umUser :: User, umMessage :: Message }
instance Indexable UserMessage where
empty = ixSet [ ixGen (Proxy :: Proxy User), ixGen (Proxy :: Proxy Message) ]
Создание набора этих отношений позволит вам запрашивать пользователей с помощью сообщений и сообщений для пользователей без необходимости что-либо обновлять.
Библиотека имеет очень простой интерфейс, учитывая, что она делает!
РЕДАКТИРОВАТЬ: Относительно ваших "дорогостоящих данных, которые необходимо сравнить": ixset
сравнивает только поля, которые вы указываете в своем индексе (так, чтобы найти все сообщения пользователя в первом примере , он сравнивает "весь пользователь").
Вы регулируете, какие части индексированного поля оно сравнивает, изменяя экземпляр Ord
. Таким образом, если сравнение пользователей обходится вам дорого, вы можете добавить поле userId
и изменить instance Ord User
, например, только для сравнения этого поля.
Это также можно использовать для решения проблемы курицы и яйца: что если у вас есть идентификатор, но нет ни User
, ни Message
?
Затем можно просто создать явный индекс для идентификатора, найти пользователя по этому идентификатору (с помощью userSet @= (12423 :: Id)
) и затем выполнить поиск.