A simple start

Let’s represent a game of six-player Texas hold ’em. First let’s create some ADTs to describe the functionality we want. For now, we will just represent a game of online poker play, with a Map from Position to Player to represent each seat’s stack size (how much money the have) etc.

data GameState = GameState { _seats :: Map Position Player , _stakes :: Double , _pot :: Double , _board :: Board a , _dealer :: [Card] , _generator :: StdGen }

There’s nothing crazy going on here. The GameState carries around a random number generator that is used to initialise the dealer, seat players, yadayada. The rest is self explanatory, with the Player datatype just holding together the player’s cash stack, username and holding which are all wrapped in Maybe . I won’t talk about Board yet because we’ll use it later when we bring in the IxState monad.

We can get a very simple State monad out of this obviously stateful framework. These are some really simple Game type functions we could implement that will do some of the basics.

type Game = State GameState initialiseGen :: StdGen -> Game () initialiseGen gen = do state <- get put state{_generator=gen} getNumPlayers :: Game a Int getNumPlayers = do state <- get let seats = _seats state return . length $ seats -- return . length . _seats =<< get

This implementation will get us where we need to in the short term. We could quite feasibly write a dealHands function that takes the dealer list of cards and deals two cards to each player. Equally we could deal the flop and so on.

Adding Safety – Dependent Types

One of the most important things we can have in a game of poker is safety – players don’t expect to have a flop dealt after one has already been dealt. Furthermore, an incorrect is a massive disaster – we have to pay out any mistakes and lose the trust of our users. The standard way would be to write an isFlopDealSafe function that checks whether an action is safe and then throws an error. This is somewhat unpleasant, and importantly leaves us open to programmer error, especially over time when there will be myriad state-safety checks. Furthermore it’s not going to be particularly composable across different game modes. Lastly, though in a client-server interaction we would have to handle malicious actions, we would not like to have any incorrect actions generated in standard runtime, and we would like this to statically checked. This means that we want at least a little type level stuff going on.

For this kind of logic, we cannot use a standard state monad:

-- what we have right now GameState -> (r, GameState) -- what we want GameState i -> (r, GameState f)

Where i indicates a marker type of some initial state, f is the marking type of the final state, and r is the type of the result that we can then use in monad-like code. However, before we get there, let’s look at a dependent type implementation of GameState that will allow us to get the kind (hehe) of type-safety that a Coq user might start to at least respect. Here is a simple implementation of a type safe board implementation:

{-# LANGUAGE GADTs #-} -- Allows us to declare the GADT "Board" {-# LANGUAGE DataKinds #-} -- Also pushes the Street declaration to the kind level as well as the type level data Street = Initial | PreFlop | Flop | Turn | River data Board :: Street -> * where RiverBoard :: Card -> Board Turn -> Board River TurnBoard :: Card -> Board Flop -> Board Turn FlopBoard :: (Card, Card, Card) -> Board PreFlop -> Board Flop PreFlopBoard :: Board Initial -> Board PreFlop InitialTable :: Board Initial

Here we use the DataKinds and GADTs extensions so that we can create data constructors on the type level and create our own custom GADT Board . Here Street will now live at both the type level and kind level, and the data constructors of Street ( Initial , PreFlop etc.) will live at both the value and type level.

In this representation we have a kind Board that is parameterised by a type of kind Street . This looks akin to the GameState i semantics we were looking for earlier, as the current Board is parameterised by a type Street .

Furthermore, and equally importantly, the only ways that we can construct a Board a type is by having a previous Board b with a type b that can be used to create a Board a . What could break this code? I guess someone adding a constructor that allows us to create a Board River from a Board Flop . That’s not bad – we’ll just make sure that no touches this. Let’s look at how we now parameterise GameState :

{-# LANGUAGE KindSignatures #-} -- Allows us to specify the kind of a data GameState (a :: Street) = GameState { _seats :: Map Position Player , _stakes :: Double , _pot :: Double , _board :: Board a , _dealer :: [Card] , _generator :: StdGen }

In this representation of GameState , the type GameState a is dependent on the type of the current Board a . This has enabled us to talk about the current state of the game, such as “has the flop been dealt?”, through the type of the a in Board a . For cleanliness we also said that a has kind Street , just to make sure, even though none of our constructors would allow us to escape the strict restraint of a Board constructor requiring a Board (a :: Street) type (we use the KindSignatures extension for this).

Using the Indexed State Monad to get back statefulness

We now need the aforementioned State i -> (r, State f) function signature in order to implement something meaningful with our new GameState representation – the indexed State monad, or IxState . The IxState monad is quite simple – it just indicates that the type of our state variable in State StateType can change. In fact, it could change quite drastically to any * kind at all, but we won’t be looking at that here.

We can either use the IxState basic monad or the transformer IxStateT . In this case we will be using the simple monad, since we have no existing monad instance and no particularly interesting functionality we want to wrap from some pre-existing monad. One could imagine using IxStateT IO ... or IxState Either ... if we need to capture I/O or some notion of failure in the computations. Here is the declaration of our Transition type, which brings us from one street to another, and Game , which just keeps us on the same street, such as a player making a bet or folding.

-- Using the package "indexed-extras" import Control.Monad.Indexed.State type Transition i f r = IxState (GameState i) (GameState f) r type Game i r = Transition i i a

Now the two above types represent monads containing computations of types GameState i -> (r, GameState f) and GameState i -> (r, GameState f) respectively.

Avert your eyes

Unfortunately there’s something ugly we now have to do 😦

We have the functionality we were looking for, but we need to get the lovely do syntax we all crave so much in Haskell. However, though there is a Monad instance for IxState , it loses some information by fixing the initial and final state to the same type:

(>>=) :: IxState i i a -> (a -> IxState i i b) -> IxState i i b return :: a -> IxState i i a

Instead we must use the IxState equivalent of the >>= operator, >>>= and its constant sibling >>> , with a similarly new definition for ireturn :

(>>>=) :: IxMonad m => m i j a -> (a -> m j k b) -> m i k b ireturn :: a -> m i i a

These two types are clearly structurally similar to the vanilla >>= but contain two much information to be mapped to any regular Monad instance.

In order to pull do notation out of this new bind operator, we use the -XRebindableSyntax language extension and hide the timeless Prelude.>>= in favour of our new >>>= operator:

{-# LANGUAGE RebindableSyntax #-} import Prelude hiding ((>>=), (>>), return) import Control.Monad.Indexed (>>=) :: Transition i j a -> (a -> Transition j k b) -> Transition i k b (>>=) = (>>>=) (>>) :: Transition i j a -> Transition j k b -> Transition i k b v >> w = v >>>= const w return = ireturn

We can now use do notation as normal! There really isn’t much to it – now we can pretend that we are in a regular State monad and cover our eyes to the extensions list at the top of our file.

Note: We could avoid doing the above and just use the >>>= operator inline without syntactic sugar, but doing so makes for ugly code. However, since we cannot overload the operator locally (to my knowledge), we have to keep our definitions of IxState monads to mostly one module. We can certainly use >>>= in an arbitrary module, but without getting to use do notation.

Using IxState for functionality

The Transition type will allow us to easily define state functions that map a State bound by a specific street type to the following street. Because of our definition of Board and Street , we can only define a fixed number of transitions (of course we could cheat with fixed turn cards). Inside each Transition will be an atomic action that takes us from one street to the next. For example, from Initial to PreFlop we have the hands being dealt to players, where Initial is a nascent table state for seating and refilling stacks and PreFlop is when the players first get involved. Another thing we could say is that players can only sit down on the Initial street:

dealHands :: Game Initial () dealHands = do state <- iget let seats = Map.toList $ _seats state let dealer = _dealer state let (seats', dealer') = dealGo seats dealer iput state{_seats=Map.fromList seats', _dealer=dealer'} -- Player -> Transition Initial Initial () sitPlayer :: Player -> Game Initial () sitPlayer player = do state <- iget let seats = _seats state -- listPosition enumerates the list of possible positions to sit let availablePositions = filter (not . flip Map.member seats) listPosition case availablePositions of -- This is not runtime safe yet - one could wrap Either in IxStateT -- or better yet make the availability of seating a dependent type -- parameterising GameState, so we can never receive an incorrect -- seating request and get static semantic checking! [] -> error "Couldn't sit player" (p:ps) -> do let seats' = Map.insert p player seats iput state{_seats=seats'}

When using the IxState monad we use the functions iput and iget from the indexed package. We use these just the same as normal State monads. The type inference and rebound syntax will do everything else for us! The rest is up to us to implement. The calls for running these IxState monads are runIxState and runIxStateT for IxState and IxStateT respectively.

Extending Safety with Dependent Types

The next thing to do here would be to have type safety for player actions – this one is slightly harder to do, but not too bad. All we do is product, say, the active player’s position with the current street (at the type level) and pass this as a parameter to data GameState . We can even use type level lists and the like to make this process super impressive. However, having thought about it for some time, it’s not totally clear that we don’t end up implementing the same semantics at the type level as we would at the value level. I think the main value here is in the parameterisation of GameState through which we can ensure that the client and server are on the same page. In particular we can check a client request such as Bet 1 :: Action PreFlop against GameState Flop to check whether the request matches in street by using type families to implement type-level semantics. We could for example expand this to a type-level check as to whether the player has the right stack size – but this seems excessive and produces dimishing returns to my mind…

Sources

Kwang’s post on the Indexed State Monad

These fantastic theoretical and practical explanations of IxMonad on Stack Overflow