The beauty of properly used statically typed languages

The beauty of properly used statically typed languages

The real power of programming in Scala is not in mimicking Haskell and overusing monads, but in taking advantage of its type system.

In Scala you don’t have an excuse to write “Stringly typed” code, you can cheaply create a class for every type in your application using value classes.

1
2
3
4
5
class FirstName(val value: String) extends AnyVal
class LastName(val value: String) extends AnyVal
class EmailAddress(val value: String) extends AnyVal

case class User(firstName: FirstName, lastName: LastName, emailAddress: EmailAddress)

Unlike type aliases, those are real classes (at least during compilation) and can be used to both increase the readability and enforce the correctness of your code.

Obviously, when you need the underlying value you have to write something like: firstName.value, lastName.value, etc. It gets annoying really quickly, so what can we do instead of that? If you use Shapeless you can move the additional code to the part where you create new instances:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import shapeless.tag
import shapeless.tag.@@

trait FirstNameTag
trait LastNameTag
trait EmailAddressTag

type FirstName = String @@ FirstNameTag
type LastName = String @@ LastNameTag
type EmailAddress = String @@ EmailAddressTag

case class User(firstName: FirstName, lastName: LastName, emailAddress: EmailAddress)

val user = User(
  firstName = tag[FirstNameTag][String]("first name"),
  lastName = tag[LastNameTag][String]("last name"),
  emailAddress = tag[EmailAddressTag][String]("mail@example.com")
)

s"${user.firstName} ${user.lastName}"

It was enough to ensure that I can’t use the first name as the last name. How many times did you pass function arguments in the wrong order? Sometimes tests can prevent it, sometimes they don’t. What happens when you have a real domain model? The invalid code does not even compile! It is much better than tests.

Can I get rid of more tests and be sure there are no errors? What if you not only create a separate class or type for every domain type, but also for every state or context of a type?

Imagine that the users of my application can request an email confirmation of something. They click a button and the email is sent to them. Some part of my code can look like that:

1
2
3
val recipient = tag[EmailAddressTag][String]("some string that is not an email address but I received it from the client")
def sendEmail(emailAddress: EmailAddress): Unit = ???
sendEmail(recipient)

Will it compile? Unfortunately yes. Will it pass the tests? Maybe, it shouldn’t but bad tests happen. Will it work correctly? Hell no.

Of course, you can validate the email address. You can even do it twice and repeat the validation in the sendEmail function, hopefully using the same validator. You can, but we all know repeating the validation in two or more places will be the cause of an error at some point.

1
2
3
4
5
6
7
def sendEmail(emailAddress: ValidEmailAddress): Unit = ???
def validateEmail(emailAddress: EmailAddress): Either[InvalidEmailAddress, ValidEmailAddress] = ???

validateEmail(recipient) match {
  case Left(error) => ???
  case Right(email) => sendEmail(email)
}

Is it better now? Not quite, I can still send spam to everyone because there is nothing to check whether the user verified his/her email address. But types can do even that!

1
def sendEmail(emailAddress: VerifiedEmailAddress): Unit = ???
Static typing is not about using primitive types (does anyone know the source of that illustration? I saw it on a wall in the office and just took a picture of it)
Static typing is not about using primitive types (does anyone know the source of that illustration? I saw it on a wall in the office and just took a picture of it)

What will become problematic? You are going to need DTOs for your API, some data model classes and a few lines of custom code to map between them and the domain model.

What happens when you write code like that? Sure it does not look impressive; it’s not complicated; you can hire a new programmer and that person will quickly be productive because the code is self-documenting.

We tend to be excited by complexity, just look at functional programming conference talks. Does anyone want to talk about something simple? No, the more complicated or abstract concept, the better. Really, the better?

Domain-driven design or creating domain models seems to be too simple. Not easy, just simple. In my opinion, that is exactly why it is so beautiful and useful.


Remember to share on social media!
If you like this text, please share it on Facebook/Twitter/LinkedIn/Reddit or other social media.

If you watch programming live streams, check out my YouTube channel.
You can also follow me on Twitter: @mikulskibartosz

For business inquiries, send me a message on LinkedIn or Twitter.


Bartosz Mikulski
Bartosz Mikulski * data scientist / software engineer * conference speaker * organizer of School of A.I. meetups in Poznań * co-founder of Software Craftsmanship Poznan & Poznan Scala User Group