Scala Case Classes vs Enumeration

Page content

After my previous post which was received quite nicely, I wanted to write a little bit more about the topic of designing Typesafe Domain objects, this time I’ll focus on the problem of how to model a deck of cards in Scala. I’ll go over 2 different approaches you might take when designing a solution to the problem of card games in Scala. I’ll try to show how this can be done with Case Classes (Sealed Case Objects) and Enumerations.

I’ll go over those two approaches and after showing each one, I’ll provide a brief summary with listing possible pros and cons, also I’ll share my thoughts how choosing one approach will affect project in the future. At the end of this post I’ll try to pick one approach and justify my decision.

I’ll be happy to see your comments with thoughts and your own reasoning why you would pick one and not the other.

Defining the scope of the problem

But before we start, let’s first describe the scope of the problem:

For my current playground project I want to be able to process card games in a fully type-safe manner. This will allow me to offload a lot of processing and validation to the compiler. For example I want to define orderings between Ranks and Cards, also I would like to have a way to easily compare, order and shuffle cards and decks. I would also like to have a reasonably easy way to ensure that the deck is always valid (for example no duplicated cards). Ideally I would like to have an easy way to map those entities to and from JSON.

First Approach: Case Objects

In this approach I have decided to create separate sealed case objects for both Suits and Ranks, then for each of them, I have listed all possible options, each extending Suit or Rank respectively.

This gave a quite nice interface for creating more complex objects like Card or Deck which where composed out of them. Only problem I had was the ordering of the Ranks, which is not easy to address when defining type system.

Take a look at following piece of code and analyze it yourself, below it you will find my observations:

File is also available here: https://github.com/wlk/game-arena/blob/master/app/models/v1/cardsCaseObjects.scala

In my opinion, the biggest problem here is the duplication between lines 20-33 where we define all Ranks for the cards, and lines 15-18 when we have to put all of them in to a list (so later in the code, on line 41, we’ll be using that list for ordering of the cards)

The same, but to the lesser extend happens in the Suit object and the trait, we have to explicitly list all Suits so we will be able to iterate over them.

Summary of this approach:

Pros:

  • Overall it’s very convenient approach. In the Playground object, I have defined few operations you can execute (play around with it in the Scala REPL)

  • Using sealed traits will allow to offload work related to pattern matching to the compiler

Cons: 

  • We had to split enumerating Ranks and actually ranking them into 2 separate places

My guess how this approach would affect project in the further stages:

In the real project probably we will be able to leverage pattern matching a lot which here will not be a problem, also it should be quite easy to use some JSON library to map objects. Keeping a list of Ranks and their ordering in sync is not a problem because it won’t change.

Second Approach: Enumerations

Working with Enumerations in Scala can be a little cumbersome, they seem to be added to the language a little bit on the site, outside of the mainstream.

Nonetheless they are very useful as a solution to my problem, thanks to them I was able to write 15 lines of code less and have a more consistent model

File is also available here: https://github.com/wlk/game-arena/blob/master/app/models/v2/cardsEnumeration.scala

I’ll analyze this approach in contrast to the first one. First thing that is most obvious to see is that here instead of using case classes we use enumerations :) Suits are defined in lines 5-7 and Ranks are defined in lines 9-11. By defining them in this order, we automatically get an Ordering which means we don’t have to explicitly list all of them and put them into a List like we had to do in the first case.

If you take a look further, you will actually see very little differences, only Card class definition is different - we are accepting Values as parameters, instead of object. Also creating a full deck is a little bit different.

The Playground object is exactly the same!

Let’s take a look at this approach from the pros and cons perspective:

Pros:

  • Less code to write

  • No need to define Rank ordering outside of the Rank definition

Cons:

  • Enumerations are a little bit clumsy

  • No compile-time checking of match exhaustiveness

  • This results in less type safety

My guess how this approach would affect project in the further stages:

I think even though this approach at the moment seems like a more natural fit to our domain, it’s also potentially more risky. We are abandoning compile time checking (which is a problem, especially when the rest of our code relies on it and we got used to that), also I suspect that this will require more code to integrate with JSON libraries, some of them do not have a natural interface to Enumerations

Summary

As project moves forward, at some point we have to make a decision which road to take: Should I design this functionality as Sealed Case Objects or Enumerations?

Good news is that, when designing it carefully you wouldn’t need to choose, at least for some more time. If you analyzed 2 examples I have shown, especially the code that uses our simple library (the Playground object) you will see that there is no difference in the code. This is a good indicator that we should be able to either continue developing both solutions until it will be clear which approach doesn’t scale anymore. Or focus on one approach, but with keeping in mind that we can always switch to the other one if you find some problem with it.

In my case I find the approach of Sealed Case Objects a more flexible one and also I get compile time checks, this is why I decided to go that route. I’m not worried about additional 15 lines of code because I value type safety and other advantages like better support for JSON marshalling more.

References:

BTW. Did you know that I’m available for hire?