# Existential quantification versus explicit types

I developed a card game engine for a german three player card game called Skat. The operating code lives in a StateT SkatEnv (WriterT [Trick] IO) Monad.

data SkatEnv = SkatEnv { piles :: Piles
, turnColour :: Maybe TurnColour
, skatGame :: Game
, players :: Players
, currentHand :: Hand
, skatSinglePlayer :: Hand }
deriving Show

type Skat = StateT SkatEnv (WriterT [Trick] IO)


To force the three player character on the typelevel, I decided to use a special Players datatype instead of a list, that uses an existentially quantified type PL that can hold any kind of player, that implements the Player typeclass. To make match specific information available to the Player, the chooseCard etc. functions live in a MonadPlayer Monad.

class (Monad m, MonadIO m) => MonadPlayer m where
trump :: m Trump
turnColour :: m (Maybe TurnColour)
singlePlayer :: m Hand
game :: m Game

showPiles :: m (Piles)

class Player p where
team :: p -> Team
hand :: p -> Hand
chooseCard :: (HasCard d, HasCard c, MonadPlayer m)
=> p
-> [CardS Played]
-> [CardS Played]
-> Maybe [d]
-> [c]
-> m (Card, p)
=> p
-> CardS Played
-> m p
=> p
-> m Card

data PL = forall p. (Show p, Player p) => PL p

data Players = Players PL PL PL
deriving Show


By using the MonadPlayer interface I can use my Skat monad for calling the Player's functions without exposing unallowed information, e.g. the other players' cards.

instance MonadPlayer Skat where
trump = getTrump <\$> P.game
turnColour = gets turnColour
singlePlayer = gets skatSinglePlayer
game = gets skatGame


I used the PL wrapper, to be able to implement different players in seperate modules, to keep the code clean and extendible, since I have many different players, e.g. an online player that communicates via a socket or a bot, that heuristically decides which card to play.

Another concern for me is the need of the MonadIO restriction for the MonadPlayer typeclass. This is needed, because my online player for example needs to send messages via a socket, which obviously needs to live in IO, although the bot player could perfectly deal without it. But since I have one common Player interface, I have to make IO available to every player.

Is it better style, not to use the PL wrapper but implement the Players type as

data Players a b c = Players a b c


This would lead to type variables all over the place, especially for everything SkatEnv related.

I left out many implementation details, e.g. many small type definitions. Please let me know, if you need more details about a specific type.

Can you make Player a datatype instead of a typeclass? The datatype can have exactly the same methods but with the first argument elided. Make Player an instance of Show, and voilá, data Players = Players Player Player Player. One implications is that a bot and a human player are now both just values of the same type, Player. Thus the chooseCard, chooseCardOpen and onCardPlayed of a bot can yield a human with a hand and cards on the table mid-game, and vice versa. I'd love to have the freedom to have a bot play the first few rounds for me, yield to me when the game starts going, and play out the last couple of rounds when I feel victory is secure! And as you note, you already opted to give every player type MonadIO, anyway, so these types are not really looser.
• This is actually a pretty good idea, although I'm afraid it does not work for me, because my different player types should be able to store additional data they need, to process chooseCard etc. I would have to add these data fields to the general Player data type, which does not fit the idea of an abstract player interface, that is independent of the implementation. – Erich Apr 14 at 8:44
• It is supposed to be a library, so I really need the extensibility. Parameterizing Player is an option, but I feel like it is not necessarily a style improvement over what I currently have :/ How do you feel about this? – Erich Apr 15 at 13:48