Chapter 9
Type System

This chapter begins with an overview of type systems in general and highlights the main difference between static and dynamic typing. Then it shows the main features of the powerful Scala type system. The different types of polymorphisms are examined showing how Scala excels compared to Java thanks to ad hoc polymorphism through type classes. Type bounds is another powerful concept shown in this chapter. Roughly speaking, a bound lets you restrict the set of possible types that can be used in your data structures.

Due to the presence of both subtype and parametric polymorphism, you are forced to face, sooner or later, the concept of variance. You'll find out how to define your data structures as covariant, contravariant or invariant, and use bounds to satisfy the compiler complaints. You'll also meet other Scala type system niceties such as: self-type annotations, self-recursive types and abstract type members. Finally, you'll see how Scala let you simulate dynamic typing that allows you to deal with some situations where static typing is not a feasible solution.

Take into account that Scala's type system is a big subject that cannot be fully covered in a chapter or two. You can easily write an entire book about it, and the shapeless project (https://github.com/milessabin/shapeless) is a proof of how complex and powerful it can be. In this book you'll learn about Scala's type system features that will help you survive in your day-by-day coding sessions.

WHAT IS A TYPE SYSTEM?

A type system is a tractable syntactic method for proving the absence of certain program behaviors by classifying phrases according to the kinds of values they compute.” This is the definition given by Benjamin C. Pierce in his excellent book Types and Programming Languages.

Put simply, a type is something that describes a set of values that have operations in common. For instance, the type Int represents a set of integer numbers that support operations like addition, multiplication and so on.

A good type system gives you guarantees about the soundness (correctness) of your programs. It does not let you apply an operation to a value of the wrong type. For example, you cannot pass a String to a function that expects a List[Int] and see it fail at runtime; a thing that can instead happen in dynamically typed languages. For example, the following code wouldn't compile:

def headOrZero(xs: List[Int]): Int = xs.headOption.getOrElse(0)

headOrZero("hello") // compile error

You get an error similar to the following:

[error] type mismatch;
[error]  found   : String("hello")
[error]  required: List[Int
[error] headOrZero("hello")
[error]            ^
[error] one error found
[error] (core/compile:compileIncremental) Compilation failed

In the type system jargon you say that that code doesn't type check. The most important thing to understand about this simple example is that, in a statically typed language, the failure happens at compile-time while in a dynamically typed one you run across this at runtime. This is something you, hopefully, always want to avoid.

Static versus Dynamic Typing

In this section you'll see the advantages of both type systems. There's a war going on between people of both factions. We don't like wars, but both systems have advantages and disadvantages just like everything in life. We cannot deny, however, that from a typing point of view we're more about static typing. However, we understand that, sometimes, dynamic typing can be the right choice for the problem at hand. So, in this context, our motto is: “static typing whenever possible, dynamic typing only when really needed.”

In a statically typed language, type errors are caught by a compiler module, called type checker, prior to running the program. A type checker just makes a conservative approximation and gives error messages for anything that might cause a type error. This is because, analogously to the halting problem (https://en.wikipedia.org/wiki/Halting_problem), it's not possible to build a type checker that can exactly predict which programs will surely result in type errors.

In a dynamically typed language, type checking is performed at runtime, immediately before the application of each operation, to make sure that the operand type is suitable for the operation. In a dynamic type system values have types but variables don't. That is, you can have a variable and assign it a string first and an int successively.

In the dynamic world, many type errors that can be caught at compile-time arise at runtime, which is not what you really want. Imagine being called at 3:00 a.m. by your manager since your application, after six months of being in production, broke because, due to a combination of user actions, it fell into that branch of the programs where you have the type error.

In a static type system that simply can't happen.

What Static Type Systems Are Good For

These are the main advantages of a statically typed language:

  • Error detection. A static type system catches type errors at compile-time.
  • Performance. Statically typed languages provide better performance than dynamically typed ones. This is because the compiler can generate optimized code, plus there's no need to perform type check at runtime.
  • Abstractions. A good type system lets you build very reusable, polymorphic and type safe components. This improves the modularity of your systems.
  • Documentation. Last but not least, types are very good for auto documenting your code. They let you reason about your code in terms of types, which is pretty good. Correct implementations, then, are also easier because you just need to follow the types.

What Dynamic Type Systems Are Good For

Here are the main advantages of the dynamic counterpart:

  • Fast prototyping. Undoubtedly, dynamically typed languages allow faster prototyping than static ones.
  • Dealing with values whose types depends on runtime information. In these cases dynamic typing is a huge win instead of resorting to reflection and other convoluted tricks used in static languages. That said, toward the end of this chapter, you'll see what Scala has to offer in this regard.

SCALA'S UNIFIED TYPE SYSTEM

What makes Scala stand apart from Java is, without a doubt, its powerful type system. Even if you look at it from the only object-oriented perspective, Scala's type system is much better than Java's. In this regard, Scala has a unified type system in that there's a top type, Any, and a bottom type, Nothing. There are no orphans like the Java's primitive types. Figure 9.1 shows Scala's class hierarchy.

Illustration depicting Scala’s class hierarchy.

Figure 9.1

In Scala all values are instances of a class, included functions. For instance, the following two definitions of a function from Int to Int are equivalent. The first is just syntactic sugar for the second:

scala> val f: Int => Int = _ + 1
f: Int => Int = <function1>
scala> f(42)
res1: Int = 43

scala> val f: Function1[Int, Int] = new Function1[Int, Int] {
     |   override def apply(x: Int): Int = x + 1
     | }
f: Function1[Int,Int] = <function1>

scala> f(42)
res2: Int = 43

As you can see, a one-parameter function is just an instance of the Function1[A, B] class where A is the type of the parameter and B the return type.

The superclass of all classes is Any and it has two direct subclasses. AnyVal and AnyRef represent two different class worlds: value classes and reference classes, respectively. Value classes correspond to the primitive types of Java. So Java's primitives are, in Scala, classes. You don't need boilerplate machinery to wrap primitives in wrapper classes when needed. Scala does it for you. You don't even have to worry about performance because, at the JVM level, Scala uses primitives wherever possible.

Scala also has the Unit type which you use in place of void. However, this is not the full story. Scala promotes functional programming where expressions are preferred to statements. To make a long story short, expressions always return a value, statements don't.

For example, curly braces always wrap expressions whose value is that of the last expression or Unit. This is important to know because it can save you from some pitfalls. For instance, the following line of code looks like a statement but it's actually an expression:

{ val a = 42 }

Its type is Unit, and the return value is the only inhabitant of the Unit type, that is (). Here's the proof:

scala> val b: Unit = { val a = 42 }
b: Unit = ()

Here's a common pitfall:

scala> val b = if (condition) "a"

Here, condition is some boolean value. What's the type of b? Since the compiler cannot know whether condition will be true until runtime, it is not able to infer String as the type of b. Indeed, its type is Any. This is because, if the condition is false, the returned value is (), of type Unit, and the least upper bound class between String and Unit is Any.

All other classes define reference types. User-defined classes define reference types by default and they always, indirectly, subclass AnyRef. Roughly speaking, AnyRef corresponds to java.lang.Object.

Figure 9.1 also shows implicit conversions, called views, between the value classes.

scala> val x: Int = 'a' // implicit conversion from Char to Int
x: Int = 97

The char a is implicitly converted to Int. Since Scala 2.10, you can also write your own value classes, which has some important consequences as you'll see in the next section.

Value Classes

Value classes are classes that extend AnyVal instead of the default AnyRef. There are two main advantages to this:

  • Type safety. By using a value class, you introduce a new type instead of utilizing a mere Int, for example, and the compiler can be by your side from a type safety point of view.
  • No overhead. No object is allocated for value classes.

You can see it by implementing a simple value class such as the following:

class Meter(val value: Double) extends AnyVal {
  def add(m: Meter): Meter = new Meter(value + m.value)
}

If you decompile this class using javap, you will see the following signature for the add method:

public double add(double);

Hold on! In the original signature the add method takes a Meter type, where is it now? Basically, the Meter class is not used in the final bytecode. It uses, instead, the primitive type it is wrapping; double in this case. On the other hand, if you declare your class without extending AnyVal, it will default to implicitly extend AnyRef, which will make it a reference type:

class Meter(val value: Double) {
  def add(m: Meter): Meter = new Meter(value + m.value)
}

Indeed, if you decompile it, this is what the add method looks like:

public Meter add(Meter);

There you go! The class Meter gets back in the final bytecode. So you get better type safety because you use an ad hoc type, thus restricting the set of acceptable values, at no performance cost because of the no allocation thingy.

The natural question that arises at this point is “Why don't you make every class a value class?” The answer is that you may not. There are very strict restrictions to what classes are good candidates to become value classes:

  • A value class must have only a primary constructor with exactly one val parameter whose type is not a value class. Since Scala 2.11 the parameter can also be private val.
  • It may not have specialized type parameters.
  • You may not define classes, traits or objects inside a value class. Also you may not define the equals and hashCode method.
  • A value class must be a top-level class or a member of a statically accessible object.
  • It may have only defs as members.
  • It may not be extended by another class.

The most common use case for a value class is in the pimp-my-library pattern, which is when you extend an existing type through an implicit class. For example, you can think of adding the method stars to the Int class, which builds a string composed of N star characters, where N is the Int:

// private val allowed since Scala 2.11.0
implicit class IntOps(private val x: Int) extends AnyVal {
  def stars: String = "*" * x
}

val review: String = 5 stars

println(s"review: $review")

The output is:

review: *****

Although value classes are a huge win, what makes Scala stand, as a language, is that it provides three types of polymorphism, which is the subject of the next section.

POLYMORPHISM

Basically there are three types of polymorphism: Subtype, Parametric, and Ad hoc. Scala offers all of them. Java, on the other hand, has just subtype and parametric polymorphism. The king of statically typed functional programming languages, Haskell, has only parametric and ad hoc polymorphism. Even if this could sound more limiting than Scala you'll see that not having subtype polymorphism makes your life easier as a developer because you don't need to worry about variance (subject of the next section). Furthermore, ad hoc polymorphism is much more powerful than subtyping, as you'll see when you meet type classes.

Subtype Polymorphism

This type of polymorphism is typical for object-oriented languages. The traditional example is an abstract superclass, Shape, with subclasses Rectangle, Square and Circle:

trait Shape {
  def area: Double
}

class Rectangle(val width: Double, val height: Double) extends Shape {
  override def area: Double = width * height
}

class Square(val width: Double) extends Shape {
  override def area: Double = width * width
}

class Circle(val radius: Double) extends Shape {
  override def area: Double = math.Pi * radius * radius
}

val shape: Shape = new Rectangle(10, 5)

println(s"Area: ${shape.area}")

The output is:

Area: 50

Basically, you have a common interface and each implementation provides its own meaning for the methods exposed by the interface—trait in Scala jargon. We won't spend too much time on subtype polymorphism since you are already used to it from other object-oriented languages, such as Java, C#, C++ and so on.

Parametric Polymorphism

If subtype polymorphism is canonical in the object-oriented world, the parametric one is typical in functional languages. Indeed, when programmers talk about polymorphism, without adding any detail, object-oriented developers mean subtyping while functional programmers mean parametric polymorphism.

Parametric polymorphism also exists in some object-oriented languages and sometimes it is referred to as generic programming. For example, Java added parametric polymorphism through generics in version 5.

Here is an example of a method that uses parametric polymorphism:

def map[A, B](xs: List[A])(f: A => B): List[B] = xs map f

The map method takes a List[A] and a function from A to B as input and returns a List[B]. As you can see you don't mention concrete types but rather use type parameters—hence parametric polymorphism—to abstract over types. For example you can use that method to transform a List[Int] into a List[String] or, analogously, a List[String] into a List[Int]:

val stringList: List[String] = map(List(1, 2, 3))(_.toString)

val intList: List[Int] = map(List("1", "2", "3"))(_.toInt)

Roughly speaking, whenever your methods have type parameters, you're using parametric polymorphism.

Ad Hoc Polymorphism

Type classes come from the Haskell world and are a very powerful technique used to achieve ad hoc polymorphism.

First of all, a type class has nothing to do with the concept of class of object-oriented languages. In this regard a type class is best seen as a set of types that adhere to a given contract specified by the type class. If this is not very clear, don't worry; an example is worth more than a thousand words.

Consider the equality concept. You know that in order to compare two instances of a given class for equality, in Java, you need to override equals for that class. It turns out that writing a correct equality method is surprisingly difficult in object-oriented languages.

You may know that equals belongs to Object, the superclass of all Java classes. One of the problems is that the signature of equals is the following:

public boolean equals(Object other)

This means that you need to check, among other things, that other is an instance of the class you're overriding equals for, proceed by doing ugly casts and so on. Furthermore, if you override equals you also need to override hashCode, as it's brilliantly explained by Joshua Bloch in his book Effective Java.

Analyzing all the problems regarding equals is out of scope, obviously. The complexity in defining object equality using subtyping emerges in Chapter 30 of Programming in Scala: A Comprehensive Step-by-Step Guide, 2nd Edition by Odersky, Spoon, and Venners. Its title is Object Equality and it's about 25 pages long! The authors claim that, after studying a large body of Java code, the authors of a 2007 paper concluded that almost all implementations of equals methods are faulty.

So, that said, do you have a better alternative to compare two objects for equality? Yes, you guessed it: type classes. The recipe of a type class is not complicated. It consists of just three ingredients. First of all, capture the concept into a trait:

trait Equal[A] {
  def eq(a1: A, a2: A): Boolean
}

The second thing you need to do is define a method that takes, implicitly, an instance of that trait. Typically you do it within the companion object:

object Equal {
  def areEqual[A](a1: A, a2: A)(implicit equal: Equal[A]): Boolean =
    equal.eq(a1, a2)
}

The areEqual method says: “Give me two instances of any class A for which exists an implicit instance of Equal[A] in the current scope and I'll tell you if they are equal.” So, the last ingredient is the definition of the instances of Equal[A]. For example, suppose you have the Person class and you want a case-insensitive comparison between first and last names:

case class Person(firstName: String, lastName: String)

In order to be able to compare two instances of this class you just need to implement Equal[Person] and make it available in the current scope. We'll do it in the Person companion object because that's one of the places that are searched when looking for Equal[Person] instances in scope:

object Person {
  implicit object PersonEqual extends Equal[Person] {
    override def eq(a1: Person, a2: Person): Boolean =
      a1.firstName.equalsIgnoreCase(a2.firstName) &&
             a1.lastName.equalsIgnoreCase(a2.lastName)
  }
}

You have defined the Equal type class and provided an implementation for the class Person. It sounds like a pattern and, actually, it is. In fact, in Scala, the type class concept is not first class as in Haskell. It's just a pattern that is possible thanks to implicits.

Here you can see it in use:

val p1 = Person("John", "Doe")
val p2 = Person("john", "doe")

val comparisonResult = Equal.areEqual(p1, p2)

The value of comparisonResult is true. So, type classes let you model orthogonal concerns in a very elegant way. Indeed, the equality concern has nothing to do with the model, Person, if you think about it. Furthermore, the solution provided by the type class is more type safe than that provided by the equals method. That is, the areEqual method takes two instances of the same class and not Object. You don't need ugly downcasts or other abominations.

Talking about implicit resolution, another place inspected when looking for implicit values is the companion object of the type class. So you could have defined the instance of Equal[Person] also in the Equal companion object. A thorough analysis of implicit resolution policy is out of scope, but can be found here http://eed3si9n.com/implicit-parameter-precedence-again. In order to have a direct comparison with the object-oriented solution you can reimplement the shape example, seen in the subtype polymorphism section, using type classes.

First of all, the trait that capture the concept:

trait AreaComputer[T] {
  def area(t: T): Double
}

This trait means: “Given a type T you can compute its area, which is of type Double.” You don't say anything about T. The following case classes, instead, represent the model:

case class Rectangle(width: Double, height: Double)

case class Square(width: Double)

case class Circle(radius: Double)

At this point you need to provide the method that takes a T and, implicitly, an implementation of the previous trait, plus the implementations for your model:

object AreaComputer {
  def areaOf[T](t: T)(implicit computer: AreaComputer[T]): Double =
    computer.area(t)

  implicit val rectAreaComputer = new AreaComputer[Rectangle] {
    override def area(rectangle: Rectangle): Double =
      rectangle.width * rectangle.height
  }

  implicit val squareAreaComputer = new AreaComputer[Square] {
    override def area(square: Square): Double =
      square.width * square.width
  }

  implicit val circleAreaComputer = new AreaComputer[Circle] {
    override def area(circle: Circle): Double =
      math.Pi * circle.radius * circle.radius
  }
}

Here is a usage example:

import AreaComputer._
val square = Square(10)
val area = areaOf(square)

It seems like type classes are a nicer and more powerful alternative to subtyping and, indeed, they are. Just think that, using type classes, you don't need to access the source code of a class to add a behavior. You just provide an implementation of the type class for that class and you're done.

Type classes are so important that very famous Scala libraries you may already have heard of, such as scalaz, cats, shapeless and so on couldn't even exist without them. You also saw that the concept is not that complex after all, it's just a pattern with the same three steps applied over and over.

BOUNDS

Bounds in Scala are used for two main purposes:

  • Provide evidence in the context of implicits
  • Restrict the set of acceptable types

The former is served by context bounds—the latter by upper and lower type bounds.

Context Bounds

Context bounds are just syntactic sugar that lets you provide the implicit parameter list. So, a context bound describes an implicit value. It is used to declare that for some type A, there is an implicit value of type B[A] available in scope.

As an example consider the areEqual method defined earlier in this chapter when you saw type classes:

def areEqual[A](a1: A, a2: A)(implicit equal: Equal[A]): Boolean = equal.eq(a1, a2)

You can, automatically, translate it to the analogous one that uses a context bound:

def areEqual[A: Equal](a1: A, a2: A): Boolean = implicitly[Equal[A]].eq(a1, a2)

You just need to:

  1. Change the type A to A: Equal.
  2. Remove the second parameter list where you defined your expected implicit values.
  3. Retrieve, in the body of your method, the implicit value through the implicitly keyword.

So, yes, context bound is just syntactic sugar you can use in place of explicitly defining the implicits required (no pun intended). It's just a matter of taste choosing one syntax over the other.

However, there are cases where you may not use context bounds and you're forced to fall back on the explicit syntax. For instance, when your implicit type depends on more than one type you're out of luck with the context bound approach. Take for example the concept of serialization:

trait Serializer[A, B] {
  def serialize(a: A): B
}

object Serializer {
  def serialize[A, B](a: A)(implicit s: Serializer[A, B]): B = s.serialize(a)

  // implementations of Serializer[A, B
}

The Serializer trait encapsulates the concept of taking a type A and serializing it into the B type. As you can see, the serialize method of the Serializer companion object could not use the context bound syntax for Serializer[A, B] since it takes two type parameters.

Before closing the section on context bounds let's show you a common trick used to allow easy access to type class instances. Basically, you just need to define an apply method on the companion object:

object Equal {
  def apply[A: Equal]: Equal[A] = implicitly[Equal[A]
}

This will let you rewrite the areEqual method as follows:

def areEqual[A: Equal](a1: A, a2: A): Boolean = Equal[A].eq(a1, a2)

Instead of using the implicitly[Equal[A]].eq(a1, a2) you just write Equal[A].eq(a1, a2), which is cleaner and clearer. You can do that because, as you may already know, Equal[A] is the same as Equal[A].apply.

Scala also has the concept of view bounds but since they were deprecated in 2.11 we won't cover them here.

Upper and Lower Bounds

In Scala, type parameters may be constrained by a type bound. If you define your type without using a bound there will be no restriction on the possible types one can use. Whenever you want to restrict the type you need a type bound.

If you think about it, it makes sense to desire a restriction over the type. After all, we love static typing also because it lets us restrict the function domain. On the other hand, in dynamic typing a function takes zero or more parameters of any type and returns a value of any type.

Without further ado, here is an example of upper bound:

sealed trait Animal {
  def name: String
}

case class Cat(name: String, livesLeft: Int) extends Animal

case class Dog(name: String, bonesHidden: Int) extends Animal

def name[A <: Animal](animal: A): String = animal.name

The name method is where upper bound comes into play. The A <: Animal type signature means: “Accept any type that is an Animal or any of its subclasses.” Lower bounds work in a similar fashion in that they restrict the type to its superclasses instead of its subclasses. The syntax is the following:

A >: Animal

This means that the type A must be of type Animal or any of its superclasses. In this particular case the superclass of Animal is AnyRef. While the usefulness of upper bounds is obvious, a lower bound might sound useless at first sight. However, when you meet variance in the next section, you'll see that you're forced to use lower bounds to make your code work in some scenarios.

Variance

Since Scala has both subtype and parametric polymorphism you're forced to face the concept of variance sooner or later. Consider the following scenario:

sealed trait Fruit {
  def describe: String
}

class Orange extends Fruit {
  override def describe: String = "Orange"
}

class Apple extends Fruit {
  override def describe: String = "Apple"
}

class Delicious extends Apple {
  override def describe: String = "Apple Delicious"
}

class Box[A

def describeContent(box: Box[Fruit]): String = ???

val oranges = new Box[Orange

describeContent(oranges) // does not compile

When you try to compile this code you get an error similar to the following:

type mismatch;
[error]  found   : Box[Orange
[error]  required: Box[Fruit
…

What's the problem? Basically, even if Orange is a subclass of Fruit, there's no such relationship between Box[Orange] and Box[Fruit]. This came out of mixing subtype polymorphism—the Fruit class hierarchy—with parametric polymorphism, Box[A]. The previous error message is not the full story though. Indeed, the Scala compiler is kind enough to tell you what a possible fix could be. Indeed it continued with:

[error] Note:Orange <:Fruit, but class Box is invariant in type A.
[error] You may wish to define A as +A instead.

In this specific case, it actually tells you what to do. Before following its suggestion let's see, in Table 9.1, what are the available options when variance comes into play.

Table 9.1 Variance Types

Variance Type Syntax Meaning
Covariant Box[+A] If B is a subtype of A, then Box[B] is also a subtype of Box[A]
Contravariant Box[-A] If B is a subtype of A, then Box[A] is also a subtype of Box[B]
Invariant Box[A] Even if B is a subtype of A, Box[A] and Box[B] are unrelated
class Box[+A]

You just need to put the plus sign before the type, that's all. Well, not really, since nothing comes for free. Indeed, try to make Box more interesting by adding a method to it:

class Box[+A] {
  def describe(a: A): String = ???
}

If you try to compile this code this time you'll get the following error:

covariant type A occurs in contravariant position in type A of value a
[error]     def describe(a: A): String = ???
[error]                  ^

This time the compiler does not tell you what to do. The very reason behind this error has its roots in Category Theory, a branch of mathematics, which is, obviously, out of scope in a pragmatic book like this one. However you can, almost automatically, fixing this type of error by following some rules. For example, you can fix the previous compilation error by changing the code as follows:

class Box[+A] {
  def describe[AA >: A](a: AA): String = ???
}

What are you doing here? Basically you are saying to the compiler: “Hey, I promise that my type parameter is not simply A but AA, which is an A or one of its superclasses.” This way you satisfied the contravariant position the compiler was talking about.

Now, in order to better understand variance, consider the (simplified) signature of the Function1 class of the standard library:

trait Function1[-T1, +R] {
  def apply(t : T1) : R
  …
}

As you can see it's contravariant for its input type parameter and covariant for the output one. Some examples will hopefully make the reason for this clearer.

Suppose you have the following function and an instance of Apple:

def f(apple: Apple, g: Apple => Apple): Fruit = g(apple)

val apple = new Apple

It's a simple higher-order function that applies the g function to the apple object passed in.

Now, given that variance declaration for the Function1 type, experiment a bit to understand what type of functions you can pass as g by playing with the input and output type. Of course the implementations of the examples are deliberately simple because I want you to concentrate on the variance subject, not on understanding complex implementations.

First things first, consider a function that has exactly the signature required by g, that is Apple => Apple:

val appleFunc: Apple => Apple = x => identity(x)

Of course, you can pass appleFunc to f since it matches exactly the g signature:

f(apple, appleFunc)

No problem; it compiles as expected. Now consider the following function:

val fruitFunc: Fruit => Apple = x =>
  if (x.describe == "Apple") new Apple
  else new Delicious

It goes from Fruit to Apple. Should the function f accept this type of function as g? Yes, absolutely! Since Function1 is contravariant in its input type parameter this means that it can accept the Apple type and all its superclasses and Fruit, obviously, respects this rule.

It makes perfect sense if you think about it for a moment. What can a function, which goes from Fruit to Apple, do on the input parameter of type Fruit? For sure—less specific things than on a subtype of Fruit, such as Apple. So it's safe passing a function with a less specific input type parameter or, said differently, with a contravariant type parameter.

Now that we justified the contravariant type let's try to do the same with the covariant one. Take a look again at the fruitFunc function. If you look closely, you'll notice you are already exploiting the covariance of the output type parameter for functions. Indeed, fruitFunc returns an instance of Apple in one case and an instance of Delicious, Apple's subclass, in all other cases. This is possible due to the covariance of the output type.

It's plausible for a very simple reason, that is the caller of the function expects all the methods on Apple to be available. Now, you have the guarantee that Apple's subclasses have, at least, all Apple's methods implemented. That's the reason why the only logical choice for the output type parameter is to be covariant.

At this point you could say: “OK, now I'm convinced that Function1 must be contravariant in its input type parameter and covariant in its output one. But what does this have to do with the trick used in the class Box to make it compile?” It does if you look at the class Box in a more abstract way. Indeed, its describe method takes an input type and returns an output type. Well, you can say that it is isomorphic to Function1! As a matter of fact consider the following code:

val box = new Box[Apple
val f: Apple => String = box.describe

As you can see it transforms the Box's describe method to a function through a process called eta-expansion. If you don't know it don't worry about its details; just consider it a means of coercing (converting) methods into functions.

At this point the reason the Scala compiler complained when you tried to use a covariant type in a contravariant position should start to make sense.

Of course you could constrain A to be a subclass of Fruit and delegate the Box's describe method to Fruit:

class Box[+A <: Fruit] {
  def describe[AA >: A <: Fruit](a: AA): String = a.describe
}

As you can see, again, bounds to the rescue! In this case, for AA, you have a multiple bound: one to satisfy the variance and the other to constrain it to a Fruit type.

Before closing this paragraph here is the other common type of error you can get using variance. Consider the following class:

class ContraBox[-A] {
  def get: A = ???
}

If you try to compile it you'll get the infamous error:

contravariant type A occurs in covariant position in type => A of method get

After having reasoned about Function1 and why the return type of it makes sense to be covariant you may already know the reason behind the previous error.

Just try to see the get method as a function () => A, that is a zero-parameter function. Can you spot the problem now? The type returned by a function should be covariant, while A is contravariant. Don't worry you can fix this too, this time using an upper bound:

class ContraBox[-A] {
  def get[AA <: A]: AA = ???
}

There you go; this time the code will compile.

Of course, given the practical approach that a programming book must have, we tried to oversimplify some concepts but, from a pragmatic point of view, we think this is more than acceptable.

If this is the first time you've encountered the concept of variance, don't worry if something is not completely clear now. Working with it will become natural for you.

At this point you've seen enough about Scala's type system to survive while coding in Scala. Actually bounds, variance and ad hoc polymorphism, through type classes, are enough knowledge to let you write very abstract and polymorphic Scala code. In the following sections you'll see other features of the powerful Scala type system.

OTHER NICETIES

As stated at the beginning of this chapter, Scala's type system is a subject too complex and powerful to be covered in a single chapter of a book. However in the following sections you'll see other niceties of the type system.

Take into account that in Scala you can do the same thing in many different ways. For example, given a problem to model, you can do it using type classes. On the other hand, someone else may prefer self-recursive types—you'll see them in a bit. It depends both on the problem at hand and the taste of the programmer. That said, it's important to know the tools you have and equally important to choose the right one. However this last peculiarity cannot be taught; you just learn it through practice.

Self-Type Annotations

Self-type annotations allow you to serve, mainly, two purposes:

  • Provide an alias for the this keyword.
  • Impose dependencies over a class so that, in order to be instantiated, its client needs to satisfy them otherwise the compiler will complain.

Here is an example for the first case:

trait Foo { self =>
  def message: String

  private trait Bar {
    def message: String = this.message + " and Bar"
  }

  val fullMessage = {
    object bar extends Bar

    bar.message
  }
}

object FooImpl extends Foo {
  override def message: String = "Hello from Foo"
}

println(s"Message: ${FooImpl.fullMessage}")

First of all, you can see the self-type annotation right after the Foo trait declaration. You'll see how to use the self keyword in a bit.

Indeed, the previous code compiles but there's an insidious bug that will make your stack explode because of a nasty recursion. Look at the Bar trait. The programmer's intention here was to implement the Bar's message method as the concatenation of the Foo's message method and the “ and Bar” string. However, this.message refers, recursively, to Bar's message and not to Foo's. You can easily fix this by using the self alias as follows:

private trait Bar {
  def message: String = self.message + " and Bar"
}

The rest of the code remains unchanged.

The second and more important use of the self-type annotation is to provide dependencies among types. Consider this simple example:

trait Foo {
  def message: String
}

trait Bar { self: Foo =>
  def fullMessage: String = message + " and Bar"
}

Here you're saying: “Dear compiler, the Bar trait depends on the Foo trait. This is pretty obvious since we're using the message method belonging to Foo within the fullMessage method implementation. Now, if a client of this API tries to implement the Bar trait without providing also an implementation of the Foo trait, would you be so kind to raise a compilation error?” Since the compiler is kind it will fulfill your request, indeed the following attempt wouldn't compile:

object BarImpl extends Bar

The error message is something like:

illegal inheritance;
[error]  self-type BarImpl.type does not conform to Bar's selftype Bar with Foo

That basically means: “Where is my Foo implementation?” Here's how you can fix it:

trait MyFoo extends Foo {
  override def message: String = "Hello from Foo"
}

object BarImpl extends Bar with MyFoo

println(s"Message: ${BarImpl.fullMessage}")

Now the compiler is happy and if you run the code you get the following string printed to the console:

Message: Hello from Foo and Bar

So, here is a little recap. The syntax for self-type annotations can be of two types:

  • self =>
  • self: YourType =>

The former is just an alias for the this keyword and can be useful in some situations. The latter is a constraint that won't make the code compile if the client does not satisfy it.

You can also impose multiple dependencies using the syntax:

self: Type1 with Type2 with Type3 …

This means that the client needs to provide implementations for Type1, Type2, Type3 and so on in order to make things work.

Self-type annotations are used as the foundation of the Cake Pattern, brilliantly described here http://jonasboner.com/2008/10/06/real-world-scala-dependency-injection-di/. To tell you the truth, we're not the biggest fans of the Cake Pattern when it comes to dependency injection. We prefer to use type classes or other techniques. Nevertheless, it can be useful in some situations so we suggest you go through that article.

Self-type annotations also serve as a basis for self-recursive types, which are the subject of the next section.

Self-Recursive Types

One of the advantages of using a statically typed language is that you can use the type system to enforce some constraints. Scala provides self-recursive types, also known as F-bounded polymorphic types that—along with self types—let you put powerful constraints to your type definitions.

Terminology apart, here is one of the use cases where this could be useful. Consider the following example which does not use a self-recursive type:

trait Doubler[T] {
  def double: T
}

case class Square(base: Double) extends Doubler[Square] {
  override def double: Square = Square(base * 2)
}

So far so good; the compiler will not complain. The problem is that it won't complain even if you write something outrageous like the following code:

case class Person(firstname: String, lastname: String, age: Int)

case class Square(base: Double) extends Doubler[Person] {
  override def double: Person = Person("John", "Smith", 42)
}

You want to avoid something like that by enforcing a compile-time check. Enter a self-recursive type:

trait Doubler[T <: Doubler[T]] {
  def double: T
}

By using this definition of Doubler you're saying: “Hey, if someone tries to extends Doubler with a type that doesn't extend Doubler in turn (hence self-recursive), do not compile it.” In this case the previous definition of Square, which extends Doubler[Person], wouldn't compile.

Note that self-recursive types are not specific to Scala. Indeed Java uses them too. Take, for example, the Enum definition:

public abstract class Enum<E extends Enum<E>>
  implements Comparable<E>, Serializable {
…
}

E extends Enum<E> in Javanese means exactly E <: Enum[E].

F-bounded polymorphic types are of great help, but sometimes they are not enough to enforce the constraints you need. Indeed, the previous definition of Doubler still has one problem. Consider the next code:

trait Doubler[T <: Doubler[T]] {
  def double: T
}

case class Square(base: Double) extends Doubler[Square] {
  override def double: Square = Square(base * 2)
}

case class Apple(kind: String) extends Doubler[Square] {
  override def double: Square = Square(5)
}

Can you spot the problem? Look at the Apple definition, which extends Doubler[Square] instead of Doubler[Apple].

This code compiles because it respects the constraint put by the Doubler definition. Indeed Square extends Doubler so it can be used in Apple. Sometimes this is what you want in which case the self-recursive type will do. In cases when you don't want this to happen a self type can work this out:

trait Doubler[T <: Doubler[T]] { self: T =>
  def double: T
}

Now if you try to compile the previous definition of Apple, the compiler will complain by saying something like:

error: illegal inheritance;
 self-type Apple does not conform to Doubler[Square]'s selftype Square
       case class Apple(kind: String) extends Doubler[Square] {
                                              ^

Abstract Type Members

In Scala, besides abstract methods and fields, you can also have abstract types within your trait or abstract classes. Here is a simple example:

trait Food

class Grass extends Food {
  override def toString = "Grass"
}

class Fish extends Food {
  override def toString = "Fish"
}

trait Animal {
  type SuitableFood <: Food

  def eat(food: SuitableFood): Unit = println(s"Eating $food…")
}

class Cow extends Animal {
  type SuitableFood = Grass
}

class Cat extends Animal {
  type SuitableFood = Fish
}

Look at the definition of SuitableFood within the Animal trait. Using that declaration you're just saying: “The type SuitableFood is abstract and it's a subclass of Food.” The classes that extend Animal are responsible for refining the type definition. For example, Cow defines the Grass type as SuitableFood. Similarly, the Cat class refines SuitableFood using the Fish type. Now, the following code compiles as:

val grass = new Grass
val cow = new Cow

val fish = new Fish
val cat = new Cat

On the other hand, if you try to feed a cow with fish and/or a cat with grass it won't work:

cow.eat(fish) // won't compile

cat.eat(grass) // won't compile

At this point you can object: “Hey, I could have done the same thing using a type parameter instead of an abstract type member.” You're right. Indeed, the Animal hierarchy could have been implemented as follows:

trait Animal[SuitableFood <: Food] {
  def eat(food: SuitableFood): Unit = println(s"Eating $food…")
}

class Cow extends Animal[Grass

class Cat extends Animal[Fish]

The result is the same. At this point the question is: “When to prefer abstract type members to type parameters?” Well, many times it's just a matter of taste. However, when the number of type parameters is not just one, the abstract type approach could make your code easier to read.

Furthermore, we use the following rule of thumb, but take it with a grain of salt and evaluate case by case to choose which technique is best suited for the problem at hand.

We tend to use type parameters if we find the type does not make sense without citing its type parameter; otherwise we could opt for abstract types. For example, List, Option, Set and so on do not make much sense without citing their contained type. List of what? List[Int], List[String], and so on. On the other hand, in the previous example, Animal makes perfect sense without citing the type. It's more elegant even in code. You notice it more if you explicitly use a type annotation. Compare these two declarations:

val animal: Animal = new Cow

val animal: Animal[Grass] = new Cow

Moreover, there are corner cases where abstract type members could greatly simplify the implementation of the API and its client code. For example, Bill Venners used it for ScalaTest's fixtures, as he explains here: http://www.artima.com/weblogs/viewpost.jsp?thread=270195. Also, the very famous shapeless library, where the Scala type system is pushed to the limit, makes extensive use of abstract types. In this excellent post, Travis Brown explains brilliantly a corner case where using abstract type members made the difference: http://stackoverflow.com/questions/34544660/why-is-the-aux-technique-required-for-type-level-computations/34548518#34548518.

Dynamic Programming

Scala is a statically typed language, and this is a good thing, as you've seen so far. However, there are times when being able to use the peculiarities of dynamically typed languages can be a big plus for some types of problems.

In this regard, Scala provides two interesting mechanisms through which you can emulate dynamic programming for those parts of your application that need it. The techniques we're referring to go by the names of Structural Types and the Dynamic trait.

Structural Types

Structural types let you accomplish the so-called duck typing, typically found in dynamic languages. It can be summarized with a sentence: “If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck.”

In duck typing, a programmer is only concerned with making sure that objects behave as demanded of them in a given context, rather than ensuring that they are of a specific class.

For example, you could say that a resource is closable if its class contains the close method. Here is how you could model this requirement using structural types:

def closeResource(resource: { def close(): Unit }): Unit = {
  println( “Closing resource… ”)

  resource.close()
}

The duck typing incantation happens by defining the resource type as:

{ def close(): Unit }

It basically means: “Any type that has a zero-parameter close method, which returns Unit, is suitable to be passed to the closeResource method.” You can see it in action in the following example:

class Foo {
  def close(): Unit = println("Foo closed")
}

class Bar {
  def close(): Unit = println("Bar closed")
}

val foo = new Foo
closeResource(foo)

val bar = new Bar
closeResource(bar)

The output is:

Closing resource…
Foo closed
Closing resource…
Bar closed

Even if structural types give you the power of duck typing, they have the advantage, over a dynamically typed language, of providing some form of type safety at compile time. For example, the following code wouldn't compile:

val baz = new Baz
closeResource(baz)

The error you get is something like:

[error] …: type mismatch;
[error]  found: baz.type (with underlying type Baz)
[error]  required: AnyRef{def close(): Unit}
[error]   closeResource(baz)
[error]                 ^

This is pretty self-explanatory.

You can also require the existence of more than one method. In this case it's cleaner to define a type alias as in:

type Resource = {
  def open(): Unit
  def close(): Unit
}

def useResource(resource: Resource): Unit = {
  resource.open()

  // do what you need to do

  resource.close()
}

Well, it seems like structural types have no downside, but we know that all that glitters is not gold. Indeed, the machinery behind structural types is the reflection and, as such, it has a non-trivial runtime cost. This is one of the reasons why you should strive to avoid structural types as much as you can. For instance, both of the previous examples can be elegantly solved using type classes and, by now, you should also know how to do it.

Dynamic Trait

Structural types let you write generic code that will work, provided that a class has the given methods. The Dynamic marker trait, on the other hand, address the somewhat dual problem. It lets you pretend that an object has fields and/or methods that actually do not exist at declaration time. An example will make this clear:

import scala.language.dynamics

class Magic extends Dynamic {
  def selectDynamic(field: String): Unit = println(s"You called $field")
}

val magic = new Magic

magic.foo
magic.bar

The output of the previous code is:

You called foo
You called bar

The first thing to do is import the feature, since it's disabled by default. Alternatively, you can add it to the scalacOptions key in your SBT build file as follows:

scalacOptions += "-language:dynamics"

As you can see, even if the Magic class does not contain the foo and bar fields, you can still call them because it extends Dynamic. The selectDynamic method is where the calls to fields are rooted. The string passed as parameter is the name of the field you called.

You can also easily chain calls:

class Magic extends Dynamic {
  def selectDynamic(field: String): Magic = {
    println(s"You called $field")

    this
  }
}

val magic = new Magic

magic.foo.bar

The output will be the same as the previous example. As you may have guessed the trick here is that, instead of Unit, the method returns this.

Apart from fields, you can also fake methods:

class Magic extends Dynamic {
  def applyDynamic(name: String)(args: Any*): Unit =
    println(s"method '$name' called with arguments: ${args.mkString(", ")}")
}

val magic = new Magic

magic.someMethod("foo", 42, List(1, 2, 3))

The output is:

method 'someMethod' called with arguments: foo, 42, List(1, 2, 3)

The name parameter of the first section of applyDynamic is the name of the invoked method. The args parameter of its second section is a varargs of type Any, that is any number and type of arguments.

There are other methods you can use to build full incantations using the Dynamic trait, but we won't cover them for space's sake so please refer to the Scala API for more info.

SUMMARY

It's been a long road. The Scala type system is a very hard topic, and in this chapter you've seen its most used features. Don't worry if, at this time, something is not crystal clear. You'll digest these concepts while working with them.

Even from an OOP perspective, the Scala type system is superior to the Java one, since it has no distinction between primitives and reference types. This makes your code more coherent and with less boilerplate to go back and forth from the primitive world.

You've also seen that Scala offers the very powerful ad hoc polymorphism through type classes. After talking about bounds you've met the concept of variance that was introduced using a different and, maybe, more friendly approach.

In the end, after covering other goodies of the type system, you've seen what the language has to offer when it comes to dynamic typing. Take your deserved rest, then a deep breath. See you in the next chapter with an advanced type system concept and a demystification of the most common functional design patterns.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.20.156