F#: Making Illegal States Unrepresentable

Intro

I have been going HAM on F# during my off hours. In doing so, I have been writing and rewriting F# code to practice Domain Driven Design such that illegal states of a program cannot be compiled.

The Game of Thirty-Three

The basketball game Thirty-Three has the following rules:
1. Players must shoot either a Two-pointer, Three-pointer, or foul shots.
2. A player can only shoot foul shots after they scored a Two-pointer or Three-pointer (aka: field shot).
3. A player can shoot up to three foul shots after making a field shot.
4. If a player makes all three foul shots, then the player’s next shot must be a field shot.
5. If a player does not make all three foul shots then the player can shoot another field shot or turnover the ball.
6. The game is over when a player scores thirty-three or more points.

I implemented this game as a kata. In doing so, I wanted to enforce these rules such that a violation of these rules cannot be compiled.

The following code reflects a legal state:

(*Client*)
let player1, player2 = FieldShooter { Score=0 } ,
                       FieldShooter { Score=0 }

let results = (player1, player2) |> makeTwoPointer
                                 |> makeFoulShot

The output is as follows:
val player2 : FieldShooter = FieldShooter {Score = 0;}
val player1 : FieldShooter = FieldShooter {Score = 0;}
val results : FieldShooter * FieldShooter =
(FieldShooter {Score = 3}, FieldShooter {Score = 0})

The following code reflects a illegal state:

(*Client*)
let player1, player2 = FieldShooter { Score=0 } ,
                       FieldShooter { Score=0 }

let results = (player1, player2) |> makeTwoPointer
                                 |> makeTwoPointer

The output is as follows:
error FS0001: Type mismatch. Expecting a
FoulShooter * FieldShooter -> ‘a
but given a
FieldShooter * ‘b -> FoulShooter * ‘b
The type ‘FoulShooter’ does not match the type ‘FieldShooter’

Again, when a player scores a field shot (i.e. Two-pointer or Three-pointer), then they must shoot foul shots. The above code attempts to shoot two two-pointers in a row. Thus, this would result in an illegal state. Thus, the code cannot be compiled.

The domain model is the following:

(*Types*)
type Player = { Score:int }

type FieldShot = TwoPointer| ThreePointer
type FoulShots = FoulShot  | TwoFoulShots | ThreeFoulShots

type FoulShooter  = FoulShooter  of Player
type FieldShooter = FieldShooter of Player

(*Functions*)
let shoot lastShot player =
    (player.Score + lastShot)

let turnover (shooter, defender) =
    (defender, shooter)

// ** Field shots **
let fieldShot (fieldShooter, shot) =

    let player = match fieldShooter with
                 | FieldShooter player -> player

    match player.Score with
    | score when score >= 33 -> score
    | _ ->  match (fieldShooter, shot) with
            | FieldShooter player, shot -> match shot with
                                           | TwoPointer   -> player |> shoot 2
                                           | ThreePointer -> player |> shoot 3
let makeTwoPointer (shooter, defender) = 
    FoulShooter { Score= fieldShot (shooter, TwoPointer) }, defender

let makeThreePointer (shooter, defender) = 
    FoulShooter { Score= fieldShot (shooter, ThreePointer) }, defender

// ** Foul shots **
let foulShot (foulShooter, shot) =

    let player = match foulShooter with
                 | FoulShooter player -> player

    match player.Score with
    | score when score >= 33 -> score
    | _ ->  match (foulShooter, shot) with
            | FoulShooter player, shot -> match shot with
                                          | FoulShot       -> player |> shoot 1
                                          | TwoFoulShots   -> player |> shoot 2
                                          | ThreeFoulShots -> player |> shoot 3
let makeFoulShot  (shooter, defender) = 
    FieldShooter { Score= foulShot (shooter, FoulShot) }, defender

let makeTwoFoulShots  (shooter, defender) = 
    FieldShooter { Score= foulShot (shooter, TwoFoulShots) }, defender

let makeThreeFoulShots  (shooter, defender) = 
    FieldShooter { Score= foulShot (shooter, ThreeFoulShots) }, defender

(*Client*)
let player1, player2 = FieldShooter { Score=0 } ,
                       FieldShooter { Score=0 }

let results = (player1, player2) |> makeTwoPointer
                                 |> makeFoulShot
                                 |> turnover

                                 |> makeTwoPointer
                                 |> makeTwoFoulShots
                                 |> turnover

                                 |> makeThreePointer
                                 |> makeTwoFoulShots
                                 |> turnover

                                 |> makeThreePointer
                                 |> makeThreeFoulShots
                                 |> makeThreePointer
                                 |> makeThreeFoulShots
                                 |> makeThreePointer
                                 |> makeThreeFoulShots
                                 |> makeThreePointer
                                 |> makeThreeFoulShots
                                 |> makeThreePointer
                                 |> makeThreeFoulShots
                                 |> makeThreePointer
                                 |> turnover
                                 |> makeThreePointer

Conclusion

In conclusion, I have been going HAM on F# during my off hours. In doing so, I have been writing and rewriting F# code to practice Domain Driven Design such that illegal states of a program cannot be compiled.

Advertisements
1 comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: