Rewriting Java in Scala & Making Code Lovely 5 – Structural Typing

What is structural typing?

Structural typing is a way of saying that we don’t care about the type of a variable but we do care about the structure of the type of a variable.

Here’s a simple example to explain this further:

def printArea(shape : { def area : Double}) {
    println(shape + " has area " + shape.area)
}

The interesting part of the above function is the type of the argument shape; shape is a structurally typed variable. We can pass any object we want to the printArea function so long as it has a function called area that returns a Double:

case class square(val sideLength : Double) {
  def area = sideLength * sideLength
}
 
printArea(square(5.0))
// square(5.0) has area 25.0
 
case class circle(val radius : Double) {
  def area = scala.math.Pi * radius * radius
}
 
printArea(circle(10.0))
// circle(10.0) has area 314.1592653589793

We can go as far as you want with specifying structural types:

def pointlessFunction(thing : { 
    def areaWithMultiplier(multiplier : Double) : Double
    def length : Double}) = {
  // ...
}

The above function takes any type that has two functions:

  • areaWithMultiplier that has one argument of type Double and returns a Double
  • length that returns a double

But, the above example is ugly. Type aliases to the rescue:

type t = {
  def areaWithMultiplier(multiplier : Double) : Double
  def length : Double
}
def pointlessFunction(thing : t) = {
  // ...
}

Here we’ve created a type alias t that aliases the complex structural type. We then use that type in the definition of pointlessFunction in the same way we’d use any other type.

What’s the use?

Very Reusable Functions

One of the great uses of structural types is that they allow us to create very reusable functions. For example, we could write a function that sorts a list of shapes according to their size without requiring that the shapes implement a common interface:

type hasArea = { def area : Double }
 
def sortShapes(list : List[hasArea]) : List[hasArea] = {
  list.sortWith(_.area < _.area)
}
 
sortShapes(
  List[hasArea](square(5.0), square(6.0), circle(2.0), circle(3.0))
)
// Result is: List(circle(2.0), square(5.0), circle(3.0), square(6.0))

We could then pass anything we wanted to this function: shapes, countries, building plans, anything with an area function.

This becomes incredibly useful when implementing algorithms. For example, an algorithm that determines how to fit different shapes into a box could be used on any type that implemented the required method.

Legacy Classes

Structural types are also a great way to make legacy types easier to use.

We are given two classes that represent different types of customers: BusinessCustomer, IndividualCustomer. Both of these classes have a method getPhoneNumber. They don’t share a base class. We want to use a library that allows us to call these people from our computer. The typical Java approach might be something like:

public void dial(Object customer) {
  String phoneNumber;  
  if (customer instanceof BusinessCustomer) {
    phoneNumber = ((BusinessCustomer)customer).getPhoneNumber();
  } else if (customer instanceof IndividualCustomer) {
    phoneNumber = ((IndividualCustomer)customer).getPhoneNumber();
  } else {
    throw IllegalArgumentException("Argument isn't a customter " + customer);
  }
  Dialer.dial(phoneNumber);
}

Ugh.

Apart from this being a lot of code for something simple, you won’t be told of errors at compilation time!

Enter structural typing:

def dial(customer : { def getPhoneNumber : String }) = {
  Dialer.dial(customer.getPhoneNumber)
}

Much better. Compile-time safe and far easier to follow.

Duck-Typing

Structural typing is effectively duck typing. Forget littering all your classes with random interfaces IsCloneable, IsSortable, IsComparable, IsJedi… use structural typing and we can say “so long as this argument has a certain property then I’ll use it”.

This gives us some of the benefits of dynamic typing (being able to use whatever we want wherever we want) without losing the benefits of a compiler to make sure we’ve not done something silly.

Under the Covers

Under the covers Scala generates byte-code similar to the following Java:

public void printArea(Object shape) {
    double area = (Double)shape.getClass().getMethod("area").invoke(shape);
    System.out.println("Shape " + shape + " has area " + area);
}

That’s reflection at work. The up-side of this is that we can use our functions from Java code – but without any compile-time type safety. All structural types get compiled down to Object and Scala does the work at run-time to determine if the argument fits the function.

Summary

Structural typing allows us to use arguments according to what properties they have, not what type they are. This can be a very powerful tool and lead to very neat code. It also gives us the benefit of greater compile-time checking when compared against using instanceof in lots of places.

If you want to see a great real-world example of structural typing then take a look at Java to Scala – Smaller Inheritance hierarchies with Structural Typing.

Type Burdened

I’ve been trying to collect my thoughts on why I prefer Scala’s type system to any other that I’ve come across. To summarise: it has type safety and just the right amount of type burdening.

What is type burdening?

Type burdening is a consequence of type safety. The more you have to specify a type by hand the more type burdened a language is.

Some examples and numbers

Java

Java is very type burdened. I’ll arbitrarily give it a type burdening index of 1 (high values are bad).

Employee bob = new Employee();

Not only must you specify the type everywhere, but you must specify it twice in many cases.

This slows us down, makes code longer and adds no value.

ML

At the other end of the scale we have languages like ML. ML has full type inference. It will even infer the types of arguments on function calls. I’ll give it a type burdening index of 0 (low values can be bad).

fun fac 0 = 1
  | fac n = n * fac (n-1)

The above defines a function, fac, as a function that takes an integer argument and gives an integer result. This is statically typed – but there are no mentions of types anywhere. The types are inferred by the compiler unless you optionally specify a type.

The reason I say that low values can be bad (not must be bad) is that it forces you to think about what the types are whenever you look at this function. Arguably, if functions are well written and clear then this shouldn’t be hard – and you shouldn’t really care. However, we all know that bad code creeps in to any code base.

The other reason this is bad is that somebody could accidentally change the type of the function without realising (suppose they changed that multiply to a divide). This could be part of a public API and the consequences of accidentally changing a signature could be bad.

Scala

Somewhere in between we have Scala. You must specify the types of function arguments, but you don’t have to specify the types of variables (nor the return type in many cases).

def timesTwo(n : Int) = {
  val result = n * 2
  println("Result is ["+result+"]")
  result
}

Similar to ML, Scala will infer that the type of the variable result is Int. It will also infer that the return type of timesTwo is Int. However, you must specify that the argument n is of type Int. You can specify a return type if you wish, and you can specify a type for the variable result.

I like this because it forces you to specify your functions signature. If you want to change it – you have to be explicit and change it by hand.

I like this because you don’t have to specify the types of internal variables that the compiler could figure out for you.

I’m not going to give Scala a type burdening index because I can’t formally define how to come up with the number. Instead, I’ll say it is somewhere between 0 and 1 ;-)

Conclusion

I have purposefully steered clear of the argument of dynamic vs static typing. It’s a culture thing – some people like one, some people like the other, some people like both!

Type burdening gives statically typed languages a bad reputation. I think that some people have embraced dynamic typing because they have spent too long being type-burdened. Over time, I hope that more languages will reduce their type burdening and people will focus on the real strengths and weaknesses of dynamic/static typing and make a better choice for themselves.