Я использую экзистенциальные типы с моим кодом для посредничества в игре Clue .
Мой код посредничества действует как дилер.Неважно, какие типы игроков - все, что его волнует, это то, что все игроки реализуют хуки, указанные в классе типов Player
.
class Player p m where
-- deal them in to a particular game
dealIn :: TotalPlayers -> PlayerPosition -> [Card] -> StateT p m ()
-- let them know what another player does
notify :: Event -> StateT p m ()
-- ask them to make a suggestion
suggest :: StateT p m (Maybe Scenario)
-- ask them to make an accusation
accuse :: StateT p m (Maybe Scenario)
-- ask them to reveal a card to invalidate a suggestion
reveal :: (PlayerPosition, Scenario) -> StateT p m Card
Теперь у дилера может быть список игроков типа Player p m => [p]
, но это будет ограничивать всех игроков одного типа.
Это слишком сжато.Что если я захочу, чтобы у меня были разные типы игроков, каждый из которых был реализован по-разному, и чтобы они могли работать друг против друга?
Поэтому я использую ExistentialTypes
для создания оболочки для игроков:
-- wrapper for storing a player within a given monad
data WpPlayer m = forall p. Player p m => WpPlayer p
Теперь я могу легко вести разнородный список игроков.Дилер по-прежнему может легко взаимодействовать с игроками, используя интерфейс, указанный в классе типов Player
.
Рассмотрим тип конструктора WpPlayer
.
WpPlayer :: forall p. Player p m => p -> WpPlayer m
За исключением поля наспереди, это довольно стандартный haskell.Для всех типов p, которые удовлетворяют контракту Player p m
, конструктор WpPlayer
отображает значение типа p
на значение типа WpPlayer m
.
Интересный бит поставляется с деконструктором:
unWpPlayer (WpPlayer p) = p
Какой тип unWpPlayer
?Это работает?
unWpPlayer :: forall p. Player p m => WpPlayer m -> p
Нет, не совсем.Группа различных типов p
может удовлетворить контракт Player p m
с конкретным типом m
.И мы дали конструктору WpPlayer
определенный тип p, поэтому он должен возвращать тот же тип.Поэтому мы не можем использовать forall
.
Все, что мы действительно можем сказать, это то, что существует некоторого типа p, который удовлетворяет контракту Player p m
с типом m
.
unWpPlayer :: exists p. Player p m => WpPlayer m -> p