RSS Feed
Download our iPhone app
Browse DevX
Sign up for e-mail newsletters from DevX


Beginning F#: Card Tricks : Page 2

Discover the power of F# by writing a complete card-shuffling program in only 92 lines of code.

F# models paired (or tripled, quadrupled, etc.) values using tuples, which encapsulate two or more values combined into one structure. Here's an example using F# Interactive that explores how to create tuples:

   > let tupleOfTwo = ("Charlie", "Brown");;
   val tupleOfTwo : string * string
   > let tupleOfThree = ("Abraham", "Lincoln", 16);;
   val tupleOfThree : string * string * int
   > let tupleOfTuples = (tupleOfTwo, tupleOfThree);;
   val tupleOfTuples : (string * string) * (string * string * int)
Note that F# responds with the type of each value defined in the tuple. Tuples with two values are called "two-tuples;" those with three values are "3-tuples," etc. You access the first and second elements of a two-tuple with the fst and snd functions. You can access any element in an n-tuple with a let binding as shown below (these use the tuples created in the preceding example):

   > fst tupleOfTwo;;
   val it : string = "Charlie"
   > snd tupleOfTwo;;
   val it : string = "Brown"
   > 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
   > let _, secondTuple = tupleOfTuples;;
   val secondTuple : string * string * int
   > secondTuple;;
   val it : string * string * int = ("Abraham", "Lincoln", 16)
Note how the asterisk (*) separates the members of a tuple type, and the underscore ((_) is a wildcard character that means "any" or "ignore". The underscore is a way to acknowledge that one portion of a data structure exists without giving it a name—usually because you're more interested in some other portion of the data structure.

To get back to the main topic, you can model a playing card elegantly using a discriminated union whose members are represented as tuples:

   type Card =
   | RankCard of Rank * Suit
   | FaceCard of Face * Suit
   | Joker
Think of this expression as, "A Card may be a RankCard represented as a two-tuple of Rank and Suit, or a Card may be a FaceCard represented as a two-tuple of Face and Suit—or a Card may be a Joker."

Here's an F# Interactive listing that explores how to create Cards:

   > let juliusCaesar = FaceCard (King, Diamonds);;
   val juliusCaesar : Card
   > juliusCaesar;;
   val it : Card = FaceCard (King,Diamonds)
   > let lowChicago = RankCard (Two, Spades);;
   val lowChicago : Card
   > lowChicago;;
   val it : Card = RankCard (Two,Spades)
   > let courtJester = Joker;;
   val courtJester : Card
   > courtJester;;
   val it : Card = Joker
All three cards maintain both their identity as a Card and their identity as one of the Card's discriminated union members; this characteristic is one of the key enablers of functional polymorphism in F#, which you'll see more about later.

Playing cards are rarely useful by themselves; most card games are based on collections of cards.

Lists and List Sequence Expressions
There are four prominent collection types in F#.

  • Lists are native, immutable linked lists delimited by brackets [ a; b; c ].
  • Arrays are native, mutable arrays delimited by brackets and pipes [| a; b; c |].
  • Sequences are native descriptions of what a collection will contain when it's eventually enumerated. Sequences are delimited by the seq keyword and braces ({}).
  • .NET Collections are the types in the System.Collections and System.Collections.Generic namespaces.
You can use lists for the playing card application; they're easy to use, and illustrate some of the powerful features in F#. To build a deck of playing cards, begin by creating a list of the ranks, faces, and suits:

   let ranks = 
       [ Two; Three; Four; Five; Six; Seven; 
         Eight; Nine; Ten ]
   let faces = 
       [ Jack; Queen; King; Ace ]
   let suits = 
       [ Diamonds; Clubs; Hearts; Spades ]
Instead of constructing lists with an implicit set of semicolon delimited elements, you can apply a list sequence expression that instructs F# how to generate the list. The following F# Interactive listing explores list sequence expressions, for example:

   > let squares = [ for i in 1..10 -> i * i ];;
   val squares : int list
   > squares;;
   val it : int list = 
      [1; 4; 9; 16; 25; 36; 49; 64; 81; 100]
   > let squaresAndCubes = [ for i in 1..10 -> 
      [ i * i; i * i * i ] ];;
   val squaresAndCubes : int list list
   > squaresAndCubes;;
   val it : int list list = [[1; 1]; [4; 8]; 
      [9; 27]; [16; 64]; [25; 125]; 
      [36; 216]; [49; 343]; 
      [64; 512]; [81; 729]; [100; 1000]]
The list sequence expression for squaresAndCubes generates a list of lists; lists may contain anything, including primitives, other lists, collections and functions.

F# Interactive also lets you explore the two native operators F# provides to manipulate lists:

   > let firstHalf = [ "one"; "two" ];;
   val firstHalf : string list
   > let secondHalf = [ "three"; "four"];;
   val secondHalf : string list
   > let bothHalves = firstHalf @ secondHalf;;
   val bothHalves : string list
   > bothHalves;;
   val it : string list = ["one"; "two"; "three"; "four"]
   > let fullList = "zero" :: bothHalves;;
   val fullList : string list
   > fullList;;
   val it : string list = ["zero"; "one"; "two"; "three"; "four"]
The ampersand (@) operator concatenates two lists to create another list, and the double colon (::) operator prepends or appends an element to a list to create another list. Neither operator modifies the original list(s), because F# lists are immutable

F# also provides the List module with a number of useful list manipulation functions. Here are some F# Interactive examples that explore the List.concat and List.iter functions:

   > let nestedLists = [ [ 1; 2 ]; [ 3; 4 ]; [ 5; 6] ];;
   val nestedLists : int list list
   > nestedLists;;
   val it : int list list = [[1; 2]; [3; 4]; [5; 6]]
   > let flattenedList = List.concat nestedLists;;
   val flattenedList : int list
   > flattenedList;;
   val it : int list = [1; 2; 3; 4; 5; 6]
   > let reportElement element =
       printfn "Found element %A" element;;
   val reportElement : 'a -> unit
   > List.iter reportElement flattenedList;;
   Found element 1
   Found element 2
   Found element 3
   Found element 4
   Found element 5
   Found element 6
   val it : unit = ()
   > flattenedList |> List.iter reportElement;;
   Found element 1
   Found element 2
   Found element 3
   Found element 4
   Found element 5
   Found element 6
   val it : unit = ()
List.concat concatenates and flattens the elements of one or more lists. List.iter applies a function to every element in a list. The preceding code uses a let binding to define the function reportElement, and then passes that as the first argument to the List.iter function.

At the end of the listing, you can see that instead of passing flattenedList to List.iter as the second argument, you can use the pipe operator (|>) to pipe it into the intermediate function created by the expression List.iter reportElement. The pipe operator isn't necessary, but it makes many F# expressions more readable.

Lists, list sequence expressions, list operators and list functions provide everything you need to generate rank cards, face cards and a complete deck:

   let ranks = 
       [ Two; Three; Four; Five; Six; Seven; Eight; Nine; Ten ]
   let faces = 
       [ Jack; Queen; King; Ace ]
   let suits = 
       [ Diamonds; Clubs; Hearts; Spades ]
   let rankCards =
       List.concat [ for s in suits -> [ for r in ranks -> RankCard (r, s) ] ]
   let faceCards =
       List.concat [ for s in suits -> [ for f in faces -> FaceCard (f, s) ] ]
   let jokers = 
       [ Joker; Joker ]
   let fiftyTwoCards =
       rankCards @ faceCards
   let fiftyFourCards =
       fiftyTwoCards @ jokers
Now you're ready to use a functional approach to shuffling and drawing cards from these immutable decks.

Close Icon
Thanks for your registration, follow us on our social networks to keep up-to-date