Modeling domain objects

Spotlight @@ tagged types

in Scala

Valentin Willscher

Mars Climate Orbiter

1998 ⇾ Today

						case class User(email: String, name: String)

						val name  = "John N. Types"
						val email = ""
						val newUser = User(name, email) // \o/
What does that function do?

							def column(col: Map[String, String], name: String): Option[(String, String)] = ???

							def column(col: Map[ColHead, RawData], name: ColHead): Option[(PrefixedColHead, Data)] = ???

Value case classes

						case class UserName(value: String) extends AnyVal
						case class Email(value: String) extends AnyVal
						case class User(name: UserName, email: Email)

						val name  = UserName("John N. Types")
						val email = Email("")
						val newUser = User(email, name) // Yay, compile error!
  • ✔ Typesafe
  • ✔ Speaking types instead of "String"
  • ✔ Extensible

Why don't we always do this?

  • More typing
  • Naming things is hard
  • Runtime performance (boxing)

...and all over the place:

							if( > 20)
							   println("That's a very long name of yours")

(Or: many custom methods or implicit conversions)

Type aliases

Lightweight type aliases

						type UserName = String
						type Email = String
						case class User(name: UserName, email: Email)
  • ✔ Little overhead
  • ✔ Speaking types
  • ✔ Same runtime performance as primitives

						User("", "John N. Types") // \o/

👎 No typesafety 👎

Two worlds

Show me your runtime-types

										val x = "hi"

How many compiletime-types?

										val x0 = "hi"
										val x1: Any = x0
										val x2: AnyRef = x0
										val x3: Object = x0
										val x4: = x0
										val x5: CharSequence = x0
										val x6: Comparable[_] = x0
										val x6b: Comparable[String] = x0
										val x7: String = x0
val x8: ??? = x0 val x9: ??? = x0

Changing types

						val x: String = "hi"
						x.isInstanceOf[String] //true

						val iAmNoString: Any = "hi".asInstanceOf[Any]
						iAmNoString.isInstanceOf[CharSequence] //still true
						iAmNoString.isInstanceOf[String] //still true

We can't change the runtime-type!
What about compiletime-types?

						val x1: String = "hi"
						val x2: Any = x1 //works
						val x3: String = x1 //works
						val x4: String = x2 //nope
						//x2 is not of compiletime-type String

We made scalac lose types!

Adding types

Can we also add types?

						trait T
						val x: String with T = "hi" //nope

Compiler knows that "hi" is not of type T

								trait T
								val x: String with T =
								   "hi".asInstanceOf[String with T]
								x.isInstanceOf[T] //false!

We just tagged String with T

...the heck is that good for?

Tagged types in action

						trait UserName
						trait Email
						case class User(name: String with UserName, email: String with Email)

Now, let's construct a user:

						val user = User("user", "email") //Not even close
val name = "John".asInstanceOf[String with UserName] val email = "".asInstanceOf[String with Email] val user = User(name, email) //Good val user = User(email, name) //Nope
  • ✔ Typesafe
  • ✔ As fast as primitves
  • ✔ Speaking types... but ugly 👎

						object Email {
						   trait EmailTag
						   type Email = String with EmailTag
						   def apply(email: String): Email = email.asInstanceOf[Email]
						   def unapply(email: Email): Option[String] = Some(email)
						//copy&paste for UserName

And now...

						case class User(name: UserName, email: Email)
						val user = User(UserName("John"), Email(""))
						user match {
						   case User(UserName("John"), _) => println("Hi John")
						   case User(name, _) => println(s"You aren't John but $name")
						println( // works: 4


How about methods?

					case class Email(value: String) {
					   def isGerman: Boolean = value.toLowerCase.endsWith(".de")
					Email("").isGerman // true

The FP approach!

					object Email {
					   trait EmailTag
					   type Email = String with EmailTag
					   def apply(email: String): Email = name.asInstanceOf[Email]
					   implicit class EmailOps(email: Email) {
					      def isGerman: Boolean = email.toLowerCase.endsWith(".de")
					Email("").isGerman // true


					object Email {
					   trait EmailTag
					   type Email = String with EmailTag
					   def apply(email: String): Email = email.asInstanceOf[Email]
					   def subst[F[_]](fstr: F[String]) = fstr.asInstanceOf[F[Email]]
val strings: List[String] = List("", "") val emails: List[Email] = Email.subst(strings) val stringFuture: Future[String] = Future("") val emailFuture: Future[Email] = Email.subst(stringFuture)

Lists with millions of elements? Who cares!

tagging tagged types

We can still mix up...
  • Private/hidden emails with public emails
  • Guestuser emails with admin emails or technical emails
  • Admin's private emails with admin's public emails

					trait EmailTag
					type Email = String with EmailTag

					trait PrivateTag
					type PrivateEmail = Email with PrivateTag

					trait PublicTag
					type PublicEmail = Email with PublicTag

Composition rulez!

The dark side

Be careful matching on Any... or better never do it

					val anyList: List[Any] = List(42, Email(""), "randstr")

					anyList.foreach {
					   case int: Int        => println(s"found Int: $int")
					   case email: Email    => println(s"found Email: $email")
					// case Email(value)    => println(s"found Email: $email")
					   case string: String  => println(s"found String: $string")
//Output: //found Int: 42 //found String: //found String: randstr

								val    email: Email = Email("")
								val emailAny: Any   = Email("")
println( emailAny.isInstanceOf[Email] ) // false println( email.isInstanceOf[Email] ) // true
What does scalac say? (cleaned)

							val email: String = Email.apply("");
							def email(): String =;

							val emailAny: Object = Email.apply("");
							def emailAny(): Object = Main.this.emailAny;



Type tagging summary

  • Very powerful and flexible concept
  • Top runtime performance
  • Minor edgecases
  • No constructor-constraints possible
  • Available in scalaz or shapeless
    • additional safety
    • more tooling, e.g. String @@ Email syntax
    • better type inference

Cheat sheet

Thank you & Questiontime

More stuff: