Skip to content

Instantly share code, notes, and snippets.

@perokvist

perokvist/RPS.md Secret

Last active January 1, 2016 12:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save perokvist/e56d577ea5af35edb305 to your computer and use it in GitHub Desktop.
Save perokvist/e56d577ea5af35edb305 to your computer and use it in GitHub Desktop.
Blog post - Exploring F# through modeling

#Exploring F# through modeling

While using some functional concepts in C#, I felt it was time to move on. Wanting to learn more about functional programming.

While many might suggest starting with closjure, erlang, haskell, I took a baby step. Kept my familiar IDE and went for F#.

Often you see pure F# tutorials or sessions about math, Type Providers and Agents.

In this post I´ll start modeling a rock-paper-scissors game in F# and see want we might learn. Join in!

##Introduction

We´re are going to use few concepts, you'll find that we repeatedly use the same way to solve our problems (when you only got a hammer...). I belive that the concepts are readable/understandable to some extend in the code, but you'll find links to detail documentation about the concepts used. Note, this is not a tutorial, it is how I approched learning F#, so comments and improvments are more than welcome.

##Event sourced

I´m going to make the state of the game/round based on events. So to get to the current state we need to replay all the earlier events (How we do the infrastructure for this the topic of this post). But it gives use a starting point. In C# that starting point could look like;

Func<state, event, state>

Given a state and an event we could get a new state, with the event applied. The "new" part is of importance here, we're not passing state to be modified, we return new state based on the old and the event. F# is a good friend of immutability, more on that later.

The rules of the game

In a new module I stared defining the three moves of the game as record type. This might look and feel like an C# enum, but its not.

type Move=
  | Rock
  | Paper
  | Scissors

Next I do the same for the result of a game/round.

type GameResult =
  | PlayerOneWin
  | PlayerTwoWin
  | Tie

Now lets put these two to work, defining a function that calculates the result based on two moves. The game rules. We declare the function using the let keyword, naming the function wins, it takes two parameters, both moves, but we don't need to specify the type here. F# is really good of inferring types based in usages.

let wins playerOneMove playerTwoMove=
    match playerOneMove,playerTwoMove with 
    | Move.Rock,Move.Paper -> GameResult.PlayerTwoWin
    | Move.Scissors,Move.Rock -> GameResult.PlayerTwoWin
    | Move.Paper,Move.Scissors -> GameResult.PlayerTwoWin
    | x,y when x=y -> GameResult.Tie
    | _ -> GameResult.PlayerOneWin

Then it is time for another new keyword, match. We map the two input parameters to a tuple of moves. With that we could easily match the tuple for different scenarios. Piping it with "|", feels like a switch statement :)

| Move.Rock,Move.Paper -> GameResult.PlayerTwoWin

So when playerOneMove is Rock and playerTwo move is Paper, the result is PlayerTwoWin. Yes, the wins function returns a GameResult, but its not specified. It's important that all paths returns a GameResult.

| x,y when x=y -> GameResult.Tie

If the tuple doesn't match the first three scenarios, we need to compare to see if the move are the same. We then declare two short hands for the tuple values, x and y. Then we use then when keyword and compare the two. If they match we return Tie.

But if playerTwo doesn't win and it's not a tie. What does that mean? Hmm, that playerOne wins! Ok, lets fix that.

| _ -> GameResult.PlayerOneWin

In the last case, we don´t care about the tuple of some of the tuple values. Then we could use the "_" to specify that we ignore the input and always returns PlayerOneWin if we get this far.

Ok, thats the rules as a simple function.

The Game

Ok, we have the rules of the game. Now we model the game state. Using what we learned we declare two new record types. One GameState a simple pointer where in the game we are, and then the State. This is what would be rehydrated using events the we store and replay. In the declaration of the state we now use some of the types we declared earlier and now use them as part of the declaration.

State

Note, to instantiate State you'll need to assign all properties with values, and the is no null! When instantiated the state is immutable (you can't change it).

type GameState=
    | NotStarted
    | Created 
    | Started
    | Ended
type State={
    gameState:GameState
    creatorName: string
    creatorMove: Move
}

Armed with rules and a state. Someone needs to determine what events that occur when action is taken - behavior. Actions will be declare as commands.

Commands

To create a game we could represent the action with the following command.

type CreateGameCommand={
    playerName: string
    firstMove: Move
    name:string
    id:Guid
}

Think of these as parameters to a function the represent starting the game. The player who creates the game and makes the first move. The move, the name of the game and last id for later infrastructure use, to represent the game instance.

Now we create a new function that handles the CreateGameCommand. It take two parameters, the command and the current state. Here we provide type information for the command. Each command could result in one or more events, we declare that events would be returned through list (we have not declared Event yet).

let createGame (command:CreateGameCommand) state : list<Event> =
   match state.gameState with
    | GameState.NotStarted ->
        [{ name = command.name; playerName = command.playerName};
         { move = command.firstMove; playerName = command.playerName } ]
    | _ -> List.empty

Then it gets familiar, we use match again to check the gameState. If the game is NotStarted we return two events, we look at them next. If the game is anything but NotStarted we again ignore the input and returns an empty list.

Events

For the createGame function to work we need the corresponding events. F# is picky about order, so these events need to be read before(above) createGame.

The list returned was of type list. Event is the common interface. Declared as following.

type Event = 
    interface
    end

Then we also need our two first events, implementing the event interface.

type MoveMadeEvent=
    {
    playerName:string
    move:Move
    } 
    interface Event
type GameCreatedEvent=
   {
    name:string
    playerName: string
   }
   interface Event

Note that when we return the events no types is needed. F# managed to figure this out in this case, neat.

Player Two

Ok, now we could create a game and retrieve corresponding events. Next we need the second player to be able to make a move.

So a new command is needed.

type MakeMoveCommand = {
    move:Move
    playerName:string
    id:Guid
}

And with that command, the game/round ends. Following event describes the end of the game. Here we declare result and a tuple of strings as players.

type GameEndedEvent = { result: GameResult; players : string * string } interface Event

We create a function the should handle the MakeMoveCommand called makeMove. It takes a MakeMoveCommand and a state as parameters and returns a list of events, much like createGame. Once again we match an parameter, in this case a parameter property. First we checks if the game is started and then a new function to check if the player is valid. This function needs to be declared before makeMove. The isValidPlayer is a function that take a playerName and a state, note how we pass the parameter without any (). If both these conditions are true, we calculate who won using our wins function. Based on the result we return two events, one MoveMadeEvent and on GameEndedEvent. This time we give a hint of the types to get this working. Lastly we se when the first condition don't match we ignore the input and returns an empty list.

let isValidPlayer playerName state =
    state.creatorName <> playerName
let makeMove (command:MakeMoveCommand) state : list<Event> =
    match state.gameState with
    | GameState.Started when isValidPlayer command.playerName state ->
        let result = wins state.creatorMove command.move
        [{ MoveMadeEvent.playerName = command.playerName; move = command.move };
         { GameEndedEvent.result = result; players = (state.creatorName, command.playerName) } ]
    |_ -> List.empty 

Restoring State

As stated in the beginning we're persisting the events. We now have two functions acting as handlers of the incoming commands. Both returning events ready to be stored. Persisting events isn't the topic of this post, but how would we restore the state based on events ?

Remember the C# code in the beginning ?

Func<state, event, state>

So, we could make a function that takes a new state and the replays a list of events and give us new state, using this ?

Lets!

let restoreState state (events:list<Event>) :State=
    let step (evt:Event) (state:State) =
        match evt with
        | :? GameCreatedEvent as e ->
                { gameState = GameState.Started; creatorName = e.playerName; creatorMove = state.creatorMove }
        | :? MoveMadeEvent as e when e.playerName = state.creatorName ->
                { gameState = state.gameState; creatorName = state.creatorName; creatorMove = e.move }
        | :? GameEndedEvent as e -> { state with gameState = GameState.Ended }
        |_ -> state
    List.foldBack step events state

Here we created a restoreState function, that takes state and a list of events and returns state. Inside we have yet another function matching our first concept. The function step takes one event and returns state. Inside step we match the event with its type using ":?". If the type match we creates a new state based on the event info. If there is no match, we ignore and returns the state. Lastly we need to call step for each event, using foldBack, passing a list of events and state. Note that the initial state would be instantiated outside this function.

##Lessons learned

Ok, lets list what we have done, and thus hopefully learned.

  • F# is picky with ordering and indentation.
  • We declared immutable record type, simple and complex.
  • We declared named functions using the let keyword.
  • We learned how to pass parameter with and without type info.
  • We heavily used match, for property values and types.
  • We declared an interface and implemented the same.
  • We declared functions within functions.
  • We instanciated tuples using *.

#Closing

There is still a lot of improvements that could be done. That and infrastructure is left to another time. If you found this interesting coming from a C# world a recommend watching the following session by Greg Young;

http://www.infoq.com/presentations/8-lines-code-refactoring

Other session about EventStore by Grey often contains nuggets and insights from functional programming.

If you found the modeling parts especially interesting, take a look this code and presentation about the topic.

http://fsharpforfunandprofit.com/ddd/

Enjoy!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment