Friday, December 3, 2010

Scala Duck Typing, Almost

There are a couple of different approaches to type systems, and I'm not talking about the whole static vs. dynamic thing. The nominative approach requires subtypes to extend base types explicitly. The structural approach allows types to be equivalent if they merely have the same methods. Scala supports both.

Nominative Example

trait Printable { def print :Unit }

class Nominative extends Printable {
def print { println("Nominative") }
}

If we define a function that accepts Printable instances, then it will happily accept Nominative instances, too.

def nominative(p :Printable) = {
p.print
}

nominative(new Nominative)

Structural Example

Because our Structural class below does not explicitly extend Printable, the compiler does not let us pass its instances into the nominative function, even though it has a suitable print method. And sometimes, that's exactly the kind of type safety we want.

class Structural {
def print { println("Structural") }
}

nominative(new Structural) // does NOT compile

But, other times it isn't. Scala is powerful enough to support structural types, whose definitions look like traits but without names. We can use the type keyword to give our structural type an alias.

type CanPrint = { def print :Unit }

def structural(p :CanPrint) = {
p.print
}

structural(new Nominative)
structural(new Structural) // compiles!

A nice feature here is that our Structural class was defined before CanPrint, so structural typing is useful when we must adapt old code to a new purpose.

An Interesting Idiom

Finally, let's consider an interesting non-legacy case. Suppose we want structural typing, and we also have full control over our class definitions. It sure would be nice to be able to have the compiler check that our signatures match up.

Unfortunately (or perhaps fortunately, since it's not completely clear what it should mean), the following does not compile.

class DoesNotCompile extends CanPrint {

So instead, let's use the Predef.identity generic function to ensure that our class does indeed have the correct structure.

class AnotherStructural {
identity[CanPrint](this)

def print :Unit = {
println("AnotherStructural")
}
}

If we had misspelled or forgotten the print method, our class would not have compiled.


No comments: