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( ListhasArea ) // 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.

Discussion

blog comments powered by Disqus

Colin Howe

I'm Colin. I like coding, ultimate frisbee and startups. I am VP of engineering at Conversocial