FCards - Defining Cards

07. Improving readability using matching and piping

Matching

A match statement is a very clean way of dealing with a set of possible inputs that need to be treated in different ways. It also helps reduce the number of bugs because the compiler will complain if we haven’t specified a scenario for every possible value of the input.

When picking up a card we used an if statement to test if the deck was empty. We could instead use a much more readable match statement.

// ORIGINAL

let pickupCard (hand: Card list) (deck: Card list) =
  if deck.Length = 0 then 
    failwith "No cards left!!"
  else
    let topcard = deck[0]
    hand @ [topcard]

// WITH ~~ MATCH ~~

let pickupCard (hand: Card list) (deck: Card list) =
  match deck with 
  | [] -> failwith "No cards left!!!"
  | [a] -> hand @ [a]
  | a::rest -> hand @ [a]

Line by line in the pickupCard function:

  1. match on the deck value.
  2. if it’s empty, fail with an error message
  3. if it has one item then return the hand with the item added to the end
  4. if it has an item, and some more items, then return the hand with the first item added to the end

TIP: the compiler takes the first matching test from top to bottom. Something you need to be mindful of if you have two tests that could both possibly be true.

Piping

The most useful “special” operator that I use in F# is the pipe operator |>

The pipe operator takes whatever is on the left and “pipes” it into the function on the right as the last parameter value. i.e b |> func a is the same as func a b

What this allows us to do is chain functions together to create a bigger composite function.

let add a b = a + b

let multiply a b = a * b

let chained = 
  7                 // 7
  |> add 4          // 11
  |> mutiply 6      // 66
  |> add 2          // 68 

With this knowledge we can slightly improve our newDeck calculation to

let newDeck = 
  let suits = [Hearts; Diamonds; Clubs; Spades]
  let numbers = [
    Two; Three; Four; Five; Six; Seven; Eight; Nine; Ten;
    Jack; Queen; King; Ace
  ]

  List.allPairs suits numbers
  |> List.map (fun (suit, number) -> suit number)

Code so far

type CardNumber =
  | Two 
  | Three
  | Four
  | Five
  | Six
  | Seven
  | Eight
  | Nine
  | Ten
  | Jack
  | Queen
  | King
  | Ace

type Card = 
  | Hearts of CardNumber
  | Diamonds of CardNumber
  | Clubs of CardNumber
  | Spades of CardNumber
  | Joker

let hand = [Hearts Three; Diamonds Ten; Clubs King; Joker]

let newDeck = 
  let suits = [Hearts; Diamonds; Clubs; Spades]
  let numbers = [
    Two; Three; Four; Five; Six; Seven; Eight; Nine; Ten;
    Jack; Queen; King; Ace
  ]
  List.allPairs suits numbers
  |> List.map (fun (suit, number) -> suit number)

let pickupCard (hand: Card list) (deck: Card list) =
  match deck with 
  | [] -> failwith "No cards left!!!"
  | [a] -> hand @ [a]
  | a::rest -> hand @ [a]

;;
// DO IT!
pickupCard hand newDeck