Pattern Matching and Functional Polymorphism
One of F#'s notable features is its ability to make
inferences about expressions. Here's an F# Interactive exploration showing how F# makes inferences about types:
> let list1 = [ 1; 2; 3];;
val list1 : int list
> let list2 = [ [ "apple"; "orange" ]; [ "pear"; "mango" ] ];;
val list2 : string list list
> let add x y =
x + y
;;
val add : int -> int -> int
> add 2 3;;
val it : int = 5
> add "two" "three";;
add "two" "three";;
----^^^^^^
stdin(8,5): error FS0001: This expression has type
string
but is here used with type
int.
F# Interactive's response to the first line of code above is to examine the
list1 expression and determine that it is of type
int list: a list of integers. F# similarly infers that the
list2 expression is of type
string list list: a list of lists of strings.
In the third entry, F# examines the
add function, and by looking at the use of the
x and
y arguments, infers that it is of type
int -> int -> int: a function that accepts two integers and evaluates to an integer.
You can use F#'s inference capabilities to look for specific patterns in types and expressions and then take action accordingly. This process is called
pattern matching, and you've already see it in action throughout this article. First, you used it to extract the elements of a 3-tuple:
> let tupleOfThree = ("Abraham", "Lincoln", 16);;
val tupleOfThree : string * string * int
> let x, y, z = tupleOfThree;;
val z : int
val y : string
val x : string
> x;;
val it : string = "Abraham"
> y;;
val it : string = "Lincoln"
> z;;
val it : int = 16
You also used it to extract only the relevant elements of 2-tuple in a comparison function:
let sortedWeightedCards =
List.sort
(fun (_, leftWeight) (_, rightWeight) -> leftWeight-rightWeight)
weightedCards
The
match keyword is another way to take advantage of pattern matching:
> let findTwosInTuple tuple =
match tuple with
| (2, _) -> "2 is the first member of the tuple!"
| (_, 2) -> "2 is the second member of the tuple!"
| _ -> "There isn't a 2 in this tuple";;
val findTwosInTuple : int * int -> string
> findTwosInTuple (2, 0);;
val it : string = "2 is the first member of the tuple!"
> findTwosInTuple (3, 0);;
val it : string = "There isn't a 2 in this tuple"
> findTwosInTuple(99, 2);;
val it : string = "2 is the second member of the tuple!"
Pattern matching is useful in far more ways than this article can list, but the most prominent use for the construct in F# is
functional polymorphism: allowing a function to change its behavior based on the type (or pattern) of the expression it's acting on. The playing card application provides a perfect example.
After the playing card application draws cards from the deck, you need to display the results in the console window. Here's a function called
printCard that accepts a playing card, but that behaves differently based on the card type (face card, rank card, or joker). The
match construct makes this trivial:
let printCard card =
match card with
| RankCard(rank, suit) -> printfn "%A of %A" rank suit
| FaceCard(Ace, suit) -> printfn "Bam! An Ace of %A" suit
| FaceCard(face, suit) -> printfn "Nice! %A of %A" face suit
| Joker -> printfn "Joker is wild!"
The
printCard function is polymorphic with regard to rank cards, aces, face cards and jokers. The polymorphic behavior in this case is a trivial
printfn expression, but you could easily replace that with function expressions that encapsulate complex behaviors.
The application's main function is now complete:
let main =
let shuffledCards = shuffle fiftyFourCards
let (drawnCards, remainingDeck) = draw 10 shuffledCards
let printCard card =
match card with
| RankCard(rank, suit) -> printfn "%A of %A" rank suit
| FaceCard(Ace, suit) -> printfn "Bam! An Ace of %A" suit
| FaceCard(face, suit) -> printfn "Nice! %A of %A" face suit
| Joker -> printfn "Joker is wild!"
List.iter printCard drawnCards
Here's the console output from one execution:
Bam! An Ace of Clubs
Nice! Queen of Spades
Joker is wild!
Six of Spades
Bam! An Ace of Spades
Eight of Diamonds
Nine of Clubs
Five of Clubs
Nice! King of Diamonds
Three of Clubs
As you can see by the terseness of the code you've written, F# is an elegant, logical language. You've explored the functional programming approach and some of F#'s native features, such as:
- Discriminated unions
- Tuples
- Lists and list sequence expressions
- Functions and recursion
- Pattern matching and functional polymorphism
You've created a simple yet powerful playing card application using only 92 lines of code. Stay tuned for a follow-up article that explores some of F#'s more advanced features to implement the core of any playing card game: the rules.