Я пишу клиентский интерфейс для онлайн-игры. Он структурирован как модуль Model , который представляет состояние игры, и модуль View , который отслеживает текущее состояние игры и обновляет его, используя переходы модели, т.е. функции из одного состояния в еще один. Чтобы воспользоваться преимуществами статической проверки типов, я смоделировал состояния как отдельные типы с классами типов, представляющими общие черты:
class Erring s where errors :: s -> [String]
class WithPlayers s where players :: s -> [String]
class Erring s => LoggedIn s
data LoggedOut = LoggedOut [String] deriving (Eq, Show)
instance Erring LoggedOut where errors (LoggedOut es) = es
data Ready = Ready [String] [String] deriving (Eq, Show)
instance Erring Ready where errors (Ready _ es) = es
instance LoggedIn Ready
instance WithPlayers Ready where players (Ready ps _) = ps
data NotReady = NotReady [String] [String] deriving (Eq, Show)
instance Erring NotReady where errors (NotReady _ es) = es
instance LoggedIn NotReady
instance WithPlayers NotReady where players (NotReady ps _) = ps
-- some transitions:
login :: String -> LoggedOut -> Either Ready LoggedOut
login pwd (LoggedOut es) =
if pwd == "password" then Left $ Ready [] es
else Right $ LoggedOut (es ++ ["incorrect password"])
logout :: LoggedIn s => s -> LoggedOut
logout s = LoggedOut $ errors s
Это может быть немного утомительным, когда есть десятки состояний и экземпляров, которые нужно определить, но в результате получается надежный API.
Войдите в просмотр. Для хранения состояния я хотел использовать TMVar
, чтобы и поток пользовательского интерфейса, и поток обработки сообщений от сервера могли выполнять переходы между состояниями. Поскольку каждое состояние относится к разному типу, я создал новый тип, который может представлять все возможные состояния:
data SessionState = SSLoggedOut LoggedOut
| SSReady Ready
| SSNotReady NotReady
Теперь можно определить ссылку на состояние типа TMVar SessionState
.
Теперь это кажется не совсем правильным. По сути, мне приходится определять каждое состояние дважды, один раз как тип, а второй раз как конструктор типа, обертывающий этот тип. Итак, вопросы:
TMVar
, если атомарные обновления разными потоками являются обязательными или есть ли лучший способ отслеживать состояние? TMVar
правильный путь, тогда необходимо ли определять что-то вроде SessionState
оболочки?