Вы можете связать типы storeEntity
и retrieveEntity
, добавив тег уровня типа к идентификатору вашей организации Integer
. Я думаю, что ваш дизайн API также имеет небольшую погрешность, которая не критична, но я все равно исправлю ее. А именно: User
s не должны хранить свой идентификатор. Вместо этого имейте единственную обертку типа верхнего уровня для идентифицированных вещей. Это позволяет вам написать код раз и навсегда для идентификаторов, например: функция, которая принимает сущность, у которой еще нет идентификатора (как бы вы представляли ее с помощью определения User
?), и выделяет для нее новый идентификатор - не возвращаясь и не изменяя свой класс Entity
и все его реализации. Также отдельно хранить имя и фамилию неправильно . Итак:
import Data.Tagged
data User = User
{ name :: String
, email :: String
} deriving (Eq, Ord, Read, Show)
type Identifier a = Tagged a Integer
data Identified a = Identified
{ ident :: Identifier a
, val :: a
} deriving (Eq, Ord, Read, Show)
Здесь мой Identified User
соответствует вашему User
, а мой User
не имеет аналога в вашей версии. Класс Entity
может выглядеть так:
class Entity a where
store :: Identified a -> IO ()
retrieve :: Identifier a -> IO a
publish :: a -> IO () -- or maybe Identified a -> IO ()?
instance Entity User -- stub
В качестве примера приведенного выше принципа «напиши раз и навсегда» вы можете найти удобным для retrieve
фактическое связывание возвращаемой сущности с ее идентификатором. Теперь это можно сделать единообразно для всех сущностей:
retrieveIDd :: Entity a => Identifier a -> IO (Identified a)
retrieveIDd id = Identified id <$> retrieve id
Теперь мы можем написать действие, связывающее вместе типы его хранилища и извлечения действий:
storeRetrievePublish :: Entity a => Identified a -> IO ()
storeRetrievePublish e = do
store e
e' <- retrieve (ident e)
publish e'
Здесь ident e
имеет достаточно богатую информацию о типе, поэтому мы знаем, что e'
должно быть a
, даже если у нас нет явной сигнатуры типа для него. (Подпись на storeRetrievePublish
также необязательна; здесь указана подпись GHC.) Последние штрихи:
main :: IO ()
main = storeRetrievePublish (Identified 1 (User "Thomas Meier" "tm@meier.com"))
Если вы не хотите явно определять storeRetrievePublish
, вы можете обойтись следующим:
main :: IO ()
main = do
let user = Identified 1 (User "Thomas Meier" "tm@meier.com")
store user
user' <- retrieve (ident user)
publish user'
... но вы не можете развернуть определения гораздо дальше: если вы уменьшите ident user
до 1
, вы потеряете связь между тегом type идентификатора, используемого для store
и для retrieve
и вернитесь к неоднозначной ситуации.