Indy Code - Taking a Gamble With F#: Implementing Blackjack

109
Taking a Gamble with F# Implementing Blackjack Cameron Presley @ PCameronPresley http://Blog.TheSoftwareMentor.com

Transcript of Indy Code - Taking a Gamble With F#: Implementing Blackjack

Page 1: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Taking a Gamble with F#Implementing Blackjack

Cameron Presley@PCameronPresley

http://Blog.TheSoftwareMentor.com

Page 2: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Little About Myself

Senior Software Engineer at Pilot/Flying J

Musician and Board gamer

Co-organizer of @FunctionalKnox

“Developer Betterer”

Page 3: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Goals

Explore the different types in F# and the when to use them

Demonstrate how to use these types to model Blackjack

Show how to deal with things that can fail

Page 4: Indy Code - Taking a Gamble With F#: Implementing Blackjack

TypesOverview

How to hold data

Collections

Combining different types as one

Page 6: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Holding DataTuples

Provides a way to model all possible combinations of types

A tuple of int*string would represent all combinations of integer and strings

A triple of int*string*int would represent all combinations of integer, string, and integer

Page 7: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Holding DataTuples

Page 8: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Holding DataTuples

Advantages Create on the fly Easy to break down

Disadvantages Parameters have to be in order (int*string is different from string*int) Hard to keep values in the right order (int*int*int) Doesn’t provide a lot of type safety

Page 9: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Holding DataTuples

Page 10: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Holding DataTuples When Should You Use Tuples?

When working within a function When there are few parameters

When Should You NOT Use Tuples? Representing a domain model Need more type safety Representing the same data types (i.e. int*int*int)

Can be hard to remember which element represents which

Page 11: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Holding DataRecords

Like a tuple, provides a way to model all possible combinations of a value

Key Differences Must define as a type Provides labels (i.e. dot syntax) Order doesn’t matter

Page 12: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Holding DataRecords

Page 13: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Holding DataRecords

Advantages Order doesn’t matter Named parameters Easy to retrieve a value

Disadvantages Can’t create on the fly Verbose definition

Page 14: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Holding DataRecords

When Should You Use Records? Representing domain models Working with a lot of parameters Providing better type safety

When Should You NOT Use Records? Intermediate results for a function

Page 15: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Holding DataReview

Tuples Create on the fly Easy to break down into separate values

Records Representing domain models Working with a lot of parameters Providing better type safety

Page 16: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Collections

Page 17: Indy Code - Taking a Gamble With F#: Implementing Blackjack

CollectionsSequences

Provides a way to define the collection by using a generator function

Lazy by default Only creates values when iterated

Similar to IEnumerable from C#

Page 18: Indy Code - Taking a Gamble With F#: Implementing Blackjack

CollectionsSequences

Page 19: Indy Code - Taking a Gamble With F#: Implementing Blackjack

CollectionsSequences

Advantages Can model an infinite collection Provides a way to generate all values Only computes values when needed

Disadvantages Expensive to add a value -> O(n) Retrieving an item by index is slow -> O(n) Can’t modify elements in place

Page 20: Indy Code - Taking a Gamble With F#: Implementing Blackjack

CollectionsLists

Represents a collection as two parts, the first element, and the rest of the list First element referred to as the head Rest of the list is referred to as tail

Similar to linked lists

Typically used with recursion, pattern matching, and the :: (cons) operator

Page 21: Indy Code - Taking a Gamble With F#: Implementing Blackjack

CollectionsLists

Page 22: Indy Code - Taking a Gamble With F#: Implementing Blackjack

CollectionsLists

Advantages Can define recursive functions to process element by element Adding additional elements is fast -> O(1) Can be defined with a generator function

Disadvantages Can’t model infinite series Indexing to an element is slow -> O(n) Can’t modify elements in place

Page 23: Indy Code - Taking a Gamble With F#: Implementing Blackjack

CollectionsArrays

Represents a collection as a dictionary such that the index finds the value

Long story short, similar to arrays in other languages

Typically used when lookup speed matters

Page 24: Indy Code - Taking a Gamble With F#: Implementing Blackjack

CollectionsArrays

Page 25: Indy Code - Taking a Gamble With F#: Implementing Blackjack

CollectionsArrays

Advantages Quick lookup -> O(1) Can change one element in the array with a different element -> O(1) Can model multidimensional data (2D arrays)

Disadvantages Can’t model infinite series Adding additional elements is slow -> O(n)

Page 26: Indy Code - Taking a Gamble With F#: Implementing Blackjack

CollectionsSummary

Sequences IEnumerable from C# Should use when all values can be generated by a function or when

modeling an infinite series

Lists Linked lists Great for when the number of elements can change or when processing

element by element

Arrays Similar to traditional arrays Best used when certain elements need to change or for quick access

Page 27: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Combining Different TypesDiscriminated Unions

Page 28: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Combining Different TypesDiscriminated Unions

Discriminated Unions provides a way to specify that a type can be one of many different types

The Difference between Tuples/Records and Discriminated Unions (DUs) Tuples/Records -> int*string (all combinations of integer and string) DUs -> int | string (either it’s an integer or a string)

Typically used with pattern matching

Page 29: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Combining Different TypesDiscriminated Unions

Page 30: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Combining Different TypesDiscriminated Unions

Page 31: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Combining Different TypesDiscriminated Unions

Page 32: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Combining Different TypesDiscriminated Unions Now I needed to create a new coordinate and I do this:

What’s to stop me from making this error?

Page 33: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Combining Different TypesDiscriminated Unions

Problem is that Lat and Long are part of the domain for Coordinate

Instead of modeling them as floats, they should have their own type

But float is the correct data type, so how I create a type that wraps around a float?

Single Case Discriminated Unions!

Page 34: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Combining Different TypesDiscriminated Unions

Page 35: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Combining Different TypesDiscriminated Unions

When a primitive needs to be modeled as part of the domain Single Case

When different type signatures actually model the same thing Combining different types

When there are a finite number of possibilities Enum Style

Page 36: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Data Type Recap

Holding Data Tuples, Records

Collections Sequences, Lists, Arrays

Combining Different Types Discriminated Unions

Page 37: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Modeling The Domain

Page 38: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Modeling The Domain

Goals

Identify the different models

Implementation

Page 39: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Modeling The DomainGoals

Has to be easy to read and understand Easier to maintain

Defines a ubiquitous language Provides a way for developers, QA, and users to communicate

Make illegal states unrepresentable Why allow bad data to be created?

Page 40: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Identifying Models

Usually start big and then break down to smaller pieces

How do we represent the entire game?

Define a Game type

Page 41: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Identifying Models

Page 42: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Identifying Models

What’s included in a game?

Game has Deck Players Dealer

Page 43: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Identifying Models

Page 44: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Identifying Models

Page 45: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Identifying Models

What’s a Deck? Represents all of the Cards that will be used in the Game

What’s a Card? Combination of Rank and Suit

What’s a Rank One of 13 possible values A, 2, 3, 4, 5, 6, 7, 8, 9, 10, J, Q, K

What’s a Suit One of 4 possible values Hearts, Clubs, Spades, Diamonds

Page 46: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Identifying Models

Page 47: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Identifying Models

Page 48: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Identifying Models

What are Players? Collection of Player

What’s a Player Someone playing against the Dealer Has both a Hand and a Status

What’s a Hand? Represent the Cards a Player has

Page 49: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Identifying Models

Page 50: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Identifying Models

Represents the state of the Player Blackjack -> 2 cards worth with 21 Busted -> Has a Score over 21 Stayed -> Not Blackjack and a Score of 21 or less Cards Dealt -> Hasn’t taken a turn yet

Page 51: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Identifying Models

Page 52: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Identifying Models

Represents the final result of a Player’s Hand

Result is based on whether the Player Busted or Stayed

Can only be of one value (integer)

Important to know when comparing who won

Page 53: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Identifying Models

Page 54: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Identifying Models

Competes against every Player

Similar to Player

Dealer has a Hand and a Status

Unlike Player, there’s only one, so no ID is needed

Page 55: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Identifying Models

Page 56: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Identifying Models

Talked about the components of the game

What about points?

What about the actions that a player can take?

Page 57: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Identifying Models

Page 58: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Identifying Models

Cards are worth Points based on their Rank

Face cards are worth 10 points

Point cards are worth that many (2’s worth 2, 3’s worth 3 …, 10’s worth 10)

Aces can be counted as 1 or 11

Page 59: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Identifying Models

Page 60: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Identifying Models

During the game, a participant can take one of two different actions

Draw Cards (Hit) Adds a single card to their Hand

Stay Stops drawing cards

Page 61: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Identifying Models

Page 62: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Implementing the Domain

We’ve broken down the domain into smaller pieces and identified dependencies amongst the pieces

Time to implement the pieces in a bottom-up order

Page 63: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Implementing the Domain

Page 64: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Implementing the Domain

Page 65: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Implementing the ModelsRank Can be one of 13 possible values

A, 2, 3, 4, 5, 6, 7, 8, 9, 10, J, Q, K

What type is great for modeling a finite set of values?

Page 66: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Implementing the Domain

Page 67: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Implementing the ModelsSuit Can be one of 4 possible values

Hearts, Clubs, Spades, Diamonds

How should we model this?

Page 68: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Implementing the Domain

Page 69: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Implementing the ModelsCard Contains both a Rank and Suit

What type is great for holding data?

Page 70: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Implementing the Domain

Page 71: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Implementing the ModelsDeck Contains 1 or more Cards

What are some properties of a Deck? Length will change throughout the game

Which collection type should we use to model this?

Page 72: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Implementing the Domain

Page 73: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Implementing the ModelsHand Contains the Cards for a Player

Initially starts with two cards, but can draw more during the game

How should we model this?

Page 74: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Implementing the Domain

Page 75: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Implementing the ModelsScore Represents the final value of a Hand

Can only be one value (integer)

What’s a way to wrap primitives as their own type?

Page 76: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Implementing the Domain

Page 77: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Implementing the ModelsStatus Blackjack

Player has two cards and their score is 21

Busted Player went over 21 and that’s their final score

Stayed Player decided to not take any more Cards and that’s their final score

Cards Dealt Player hasn’t taken their turn

Page 78: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Implementing the ModelsStatus Can be one of a finite number of choices

Some of the type require Score and other ones don’t

How should we model this?

Page 79: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Implementing the Domain

Page 80: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Implementing the ModelsDealer A Dealer contains

Hand Status

How should we model this?

Page 81: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Implementing the Domain

Page 82: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Implementing the ModelsPlayer A Player contains

Hand Status ID

How should we model this?

Page 83: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Implementing the Domain

Page 84: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Implementing the ModelsPlayers Represents all the players in the Game

Number of Players won’t change during the game

Each player must finish their turn before the next player can start

Which collection type should we use?

Page 85: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Implementing the Domain

Page 86: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Implementing the ModelsGame Contains

Deck Players Dealer

How should we model this?

Page 87: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Implementing the Domain

Page 88: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Implementing the ModelsPoints Every Card is worth a value based on the Rank

One of two possible values Aces can be worth 1 or 11 Everything else can be worth one value

Need to combine two different types as one, how should we model this?

Page 89: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Implementing the Domain

Page 90: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Implementing the ModelsActions Represents what a participant can do during the Game

One of two possible values Hit Stay

Finite number of choices, how to model?

Page 91: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Implementing the Domain

Page 92: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Full Implementation

Page 93: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Implementing the ModelsSummary Determined the models by using domain terms (ubiquitous language)

Started with the biggest model (Game) and broke it down into smaller pieces and then repeating the process with the smaller pieces

After finding the models, implemented the models in a bottom-up fashion

Able to define the domain in a way such that bad states are unrepresentable

Page 94: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Playing the Game

Page 95: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Playing the Game

Sometimes, pure functions can fail

How can we handle this?

We’ll look at Drawing a Card Setting up a Player

Page 96: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Playing the GameDrawing a Card Throughout the course of the Game, Cards will be drawn from the Deck

Let’s create a function, drawCard that takes a Deck as input and returns the top Card and the rest of the Deck as output

Page 97: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Drawing a Card

Are there any issues with this implementation?

What about an empty deck? Match Failure Exception -> The match cases were incomplete

How can we model a type that may have a value or not?

Page 98: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Playing the GameDrawing a Card

Introducing the Option type!

Special kind of Discriminated Union that handles when there’s a value and when there isn’t.

Let’s rewrite the drawCard function to handle an empty deck

Page 99: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Playing the GameDrawing a Card

Page 100: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Playing the GameSetting up a Player Now that we have a way to draw cards, we can now setup a Player

Remember that a Player starts off with a Hand of two cards and a Status of CardsDealt

Page 101: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Playing the GameSetting up a Player

Remember, drawCard returns an option, so we need to handle that.

Page 102: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Playing the GameSetting up a Player This code is not nearly as readable as the first solution

What are we really trying to accomplish?

Try to draw two cards, if so, return Some Player, otherwise, None

Wouldn’t it be nice if we could have the short-circuit logic of the second solution, but still have the readability of the happy path?

How can we work around all the pattern matching?

Page 103: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Playing the GameSetting up a Player By using the Maybe Builder pattern!

Known in other FP languages as Maybe.

Two parts Provides a way to short-circuit logic if the input is None (called Bind) A way to wrap a value as an option (called Return)

Page 104: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Playing the GameSetting up a Player

Page 105: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Playing the GameSetting up a Player

Page 106: Indy Code - Taking a Gamble With F#: Implementing Blackjack
Page 107: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Playing the GameSetting up a Player Problem

Creating a Player can fail because there’s not enough cards Putting in the error handling code makes it cluttered and hard to read

Solution Using the Maybe pattern, we can short-circuit our function flow to return None

before calling anything else

When to Use Lots of nested pattern matching on options

Page 108: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Wrapping Up

Overview of the different F# types and the advantages of each

Modeled and implemented the domain for Blackjack using the different types

Explored how to handle functions that can fail

Page 109: Indy Code - Taking a Gamble With F#: Implementing Blackjack

Additional Resources

Learning more about F# F# for Fun and Profit -> http://fsharpforfunandprofit.com/

Code Solution Blackjack Repo -> https://github.com/cameronpresley/Blackjack

How to find me Twitter ->@pcameronpresley Blog -> http://blog.thesoftwarementor.com