Type safe Scala — Use domain types

Jun 12, 2017
functional-programming
scala

The less efficient alternative to new types.

By domain types I mean to be able to give a more specific/well-defined semantics to an existing data type. Let’s say we have an api to work with banking account numbers, where an account number is a numeric string between nine and nineteen characters long:

def getBalance (account: String): BigDecimal = ???
def deposit(account: String, amount: BigDecimal): Unit = ???
def withdraw(account: String, amount: BigDecimal): Unit = ???

Looking at those method we can immediately spot the issue of using the primitive String data type for modelling the account number: how should we handle, in each method, the case where the account number is invalid? And the answer is… we do not!

Instead we design our API as follows:

sealed abstract class AccountNumber(value: String)
object AccountNumber {
val accountNumberRegex = “[09]{9,19}”
val errorMsg =Account number has to be a numeric string between 9 and 19 characters long.”
def fromString(str: String): Either[String, AccountNumber] =
if (str.matches(accountNumberRegex))
Right(new AccountNumber(str) {})
else
Left(errorMsg)
}
// The rest of our API methods rely now on a valid AccountNumber
def getBalance(account: AccountNumber): BigDecimal = ???
def deposit(account: AccountNumber, amount: BigDecimal): Unit = ???
def withdraw(account: AccountNumber, amount: BigDecimal): Unit = ???

Notice how our previous methods now can take for granted that they are working with valid account numbers, because the only way of constructing AccountNumber instances is by going through the code guarded by a validation check, this means that whenever our code compiles we can be sure that it is as safe as strong our validation check is. The use of the Either type is precisely to force the users of the API to deal with (or deliberately ignore) the possibility of using an invalid account number.

Improving runtime performance

One of the downsides of the previous solution is that we are creating a wrapper type which is used only at compile time to enforce invariants, but that at runtime is completely useless. Scala provides something similar to Haskell’s newtype called value classes:

Value classes are a new mechanism in Scala to avoid allocating runtime objects. This is accomplished through the definition of new AnyVal subclasses…It has a single, public val parameter that is the underlying runtime representation. The type at compile time is wrapped, but at runtime, the representation is the underlying type.

This is great although in reality the evaporation of the wrapper type is not guaranteed to disappear and some people in the Scala community are looking for other ways to achieve it.

So, if instead of using an abstract class we could use a value class:

package api
private[api] final case class AccountNumber(value: String) extends AnyVal
object AccountNumberBuilder {
// ... same as above
}

The private[api] thingy is necessary to forbid direct access to the apply method of the companion object that is automatically generated by Scala for case classes.