Tuesday, 9 April 2013

Classes and Records

I recently started writing a text parser, a foundation stage in a larger project I’ve been planning to process large numbers of card game hands for a bit of big data experimentation in F#. I began in C#, I figured I’d just get the annoying bit out of the way here quickly in the language I was most familiar with, load the library in with the F# stuff and move on from there. After about 30 minutes of sketching out the classes for data storage, it started to feel rather clunky and weighed down. I’d already got a couple of hundred lines of code and I hadn’t even started processing anything yet, for example:

 using System.Collections.Generic;  
   
 namespace HHParser  
 {  
   /// <summary>  
   /// A class that represents a 'street' and all of its actions.  
   /// </summary>  
   public class StreetActions  
   {  
     /// <summary>  
     /// Initialises a new instance of the StreetActions class.  
     /// </summary>  
     public StreetActions()  
     {  
       OrderedActions = new LinkedList<playeraction>();  
     }  
   
     /// <summary>  
     /// All the actions taken on this particular street.  
     /// </summary>  
     public LinkedList<playeraction> OrderedActions { get; private set; }  
   }  
   
   /// <summary>  
   /// A single action a player has taken in the hand.  
   /// </summary>  
   public struct PlayerAction  
   {  
     /// <summary>  
     /// The players name.  
     /// </summary>  
     public string Player { get; set; }  
   
     /// <summary>  
     /// The action the player took.  
     /// </summary>  
     public Actions Action { get; set; }  
   
     /// <summary>  
     /// The cost to the player.  
     /// </summary>  
     public float? Amount { get; set; }  
   }  
   
   /// <summary>  
   /// The actions a player can through the process of a hand.  
   /// </summary>  
   public enum Actions  
   {  
     Fold,  
     Check,  
     Call,  
     Bet,  
     Raise  
   }  
 }  
I had figured it would be a little more concise overall, but I hadn’t realised I’d get this benefit out of the gate, when porting the above:
 namespace Parser  
   
 type Action =  
   | Fold of unit  
   | Check of unit  
   | Call of float  
   | Bet of float  
   | Raise of float * float  
   
 type Streets =  
   | Preflop  
   | Flop  
   | Turn  
   | River  
   
 type PlayerAction = { player: string; action: Action }  
   
 type Street = { actions: PlayerAction list; streetType: Streets }  
A couple of discriminated unions and records and I’m presented with a far clearer version of what I’d written in the C# version. It’s also nice to not have to worry about the accessibility in getting and assigning variables, since its immutable those properties are irrelevant.
Another point of note is more subtle but bugged me when it first came up, the nullable float in the C# PlayerAction class, provided for those actions that require it, and to be kept null for those that don’t. I felt I’d lost some neatness as soon I’d written that, and while I could have just had it as a base class and specialised for actions that required the float, that too adds its own complexity, so writing the Action type in F# was refreshingly simple, especially considering how simple it is to pattern match out the correct instance when you’re trying to do some work on it. For example:
 match action with  
   | Fold -> printfn "Player folded."  
   | Check -> printfn "Player checked."  
   | Call(size) -> printfn "Player called %f." size  
   | Bet(size) -> printfn "Player bet %f." size  
   | Raise(call, raise) -> printfn "Player raised from %f to %f." call raise  
Which is already neater than getting or setting a null value based on an enum, and no chance of them falling out of sync.