Skip to main content

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  
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.


Popular posts from this blog

Constructing a Trie in F#

This post might get a bit more context next week, but essentially, in continuation of looking at data structures in F# and C# I picked prefix trees (tries) as the next step. A trie is similar to a BST except the search is cumulative. A simple example would be building a spellchecker. The spell checker can use the structure to see if a word exists quickly and efficiently.

It works by creating a new node for each part of the word, using existing nodes if they're already in place. You can see in this diagram an example structure after an insertion of several words.

In the above diagram the words ANT, AND, BATS have been added into the data structure. You'll notice that the word BATS can also be the word BAT and that becomes a part of the structure, setting a value at each node to state if this is a place that a word terminates.

1 2 3 4// Node type for storing a trie. typeTrieNode=|Nodeofchar*TrieNodelist*bool|RootofTrieNodelist
I defined the type to have 2 options. The root node,…

A Docker Experiment

Containers are a topic that have been rising in occurrence for me, for the last year or so. From using them as part of the architecture at work or for various pet projects friends have been working on. I figured it was time to experiment myself and get to grips with what people have been talking about. It seemed like a good idea to find some introductory material that would give me an overview of its uses without delving too far into the details so I could see what it actually was, so I found a PluralSight course about Docker, specifically “Docker and Containers:The Big Picture”. This gave a nice overview of what Docker is, and more importantly what it was trying to achieve and how to use it. With a bit more of an understanding, I wanted to use it for something, preferably something familiar. I decided to try setting up ElasticSearch and Kibana containers, where Kibana would visualize the ElasticSearch data. I used bits of this article along the way as a guide, if you'd prefer a more…

Self-Producing an EP Pt.5: Mixing

After some discussion we opted to mix it ourselves for a few reasons.

It's our first EP, it's not going to be heard by a huge amount of people and while it's important that it sounds good, it's expensive to get done properly and we might be able to manage 'good enough' on our own with a lot of work.Mixing as an online service seems to be the main way to go for small/starting out bands and that seems too creatively detached. There are many stories floating around of people who've sent things off for mixing and the mixer has a much different idea of how it sounds.
Maybe it's not the best choice, but it seemed an acceptable risk that we would at least attempt to have it 'look' how we want and it be a bit wonky than potentially end up with something we're not happy with in a different way. The benefit would have been being able to just ship the stems off and make it someone else's problem instead of weeks of being unsure while swimming in the…