A new Scala feature for making illegal states unrepresentable

Making illegal states unrepresentable means that we enforce invariants on the code that we write, and choose data types so that states that are invalid won’t show up in our programs. 1

By reducing the number of representable wrong states we also reduce the number of potential bugs in our program by a great deal, as well as the number of tests needed to check for invalid inputs and outputs.

If we can’t create an illegal argument of a given type, we don’t need test cases for this illegal state for any function that takes arguments of that type as inputs.

Using factory methods

One way to control the construction of an object is to make the constructor of a class private and to implement our own factory method within the companion object:

class Age private (age: Int)

object Age {
  def apply(age: Int): Either[Message, Age] = {
    if(age <= 0 || age > 130) {
      Left(InvalidAge)
    } else {
      Right(new Age(age))
    }
  }
}

Implementing apply also gives us syntactic sugar. Let’s test that in the console:

scala>  List(Age(32), Age(-1))
res0: List[Either[Message,Age]] = List(Right(Age@4e2bbd8f), Left(InvalidAge))

The only way to create an Age instance is by calling the apply method. If we want to call the normal class constructor with new, we will get an error at compile time:

scala> new Age(32)
<console>:12: error: constructor Age in class Age cannot be accessed in object $iw
       new Age(32)
       ^

Using algebraic data types

Using algebraic data types is a powerful technique for designing with types and making illegal states unrepresentable.

Algebraic data types and particularly sum types in Scala are usually encoded with case classes.

Here is an example:

sealed trait Shape
case class Circle (r: Double) extends Shape
case class Rectangle (w: Double, h: Double) extends Shape

Case classes are very convenient because the Scala compiler generates the apply method for us, and also some other useful things such as unapply, copy, hashCode, equals, or toString. And not to mention exhaustive pattern matching.

The problem with case classes prior to Scala version 2.12.2 was that we could not replace the compiler generated apply method. Trying to implement apply within the companion object would result in a compile time error.

Therefore, adding the private keyword to the constructor had no useful effect. The callers could always bypass the constructor by calling the auto-generated apply method.

There was no way to get all the benefits provided by case classes and at the same time control the construction of instances.

But there is good news!

Enforcing factory methods with case classes

With the new Scala version 2.12.2 it is now possible to replace the generated apply method within the companion object.

Here is an implementation of Shape that uses this new technique:

sealed trait Shape
final case class Circle private (r: Double) extends Shape
final case class Rectangle private (w: Double, h: Double) extends Shape

object Circle {
  def apply(r: Double): Either[Message, Circle] = {
    if(r < 0) {
      Left(ValueMustNotBeNegative)
    } else {
      Right(new Circle(r))
    }
  }
}

object Rectangle {
  def apply(w: Double, h: Double): Either[Message, Rectangle] = {
    if(w < 0 || h < 0) {
      Left(ValueMustNotBeNegative)
    } else {
      Right(new Rectangle(w, h))
    }
  }
}

With this implementation we can be absolutely sure that any Shape instance that is passed to any function in our program will be a valid instance. (Except that unfortunately it could be null, but that’s another topic.)

Example usage:

scala> val shape = Circle(42)
shape: Either[Message,Circle] = Right(Circle(42.0))

scala> val rect = Rectangle(1,-1)
rect: Either[Message,Rectangle] = Left(ValueMustNotBeNegative)

Caveats

As Frank S. Thomas pointed out, copy can still be used to create invalid state. So copy should be replaced as well, which is unfortunate but necessary:

sealed trait Shape
final case class Circle private (r: Double) extends Shape {
  def copy(r: Double = this.r) = Circle(r)
}

final case class Rectangle private (w: Double, h: Double) extends Shape {
  def copy(w: Double = this.w, h : Double = this.h) = Rectangle(w, h)
}

Another possibility is to make the copy method private. It should be enough to do it like this:

final case class Circle private (r: Double) extends Shape {
  private def copy() = ()
}

Conclusion

In Scala 2.12.2 it is now possible to replace the auto-generated apply method of case classes.

This way we have one more techniques at our hand to leverage the power of the type system and ideally make illegal states unrepresentable.