Skip to content

Ducks Unlimited

Most complaints levied against static type systems come down to verbosity and complexity required to express intent, all of which are usually the fault of the language not of type system. Type inference, parametric types, etc. are all refinements to make type systems more fluid and, err, dynamic.

One language capability that reduces a lof of ceremony is statically verifiable duck-typing. While interfaces are the usual approach to letting independent implementations serve the same role, it always bugs me that interfaces put the contract at the wrong end of the dependency, i.e. I have to implement your interface in order for you to recognize that I can quack!

I had hoped that C# 4.0's dynamic keyword would fix this, but this is the best you can do:

public class AVerySpecialDuck {
  public void Quack() {
    Console.Writeline("quack");
  }
}

public class DuckFancier {
  public void GimmeADuck(dynamic duck) {
    duck.Quack();
  }
}

new DuckFancier().GimmeADuck(new AVerySpecialDuck());

Sure, I can now call .Quack() without having to require a concrete type or even an interface, but I also could have provided a rabbit to GimmeADuck without compilation errors. And as the user of GimmeADuck there is no machine discoverable way to determine what it expects.

I solved half of this problem with Duckpond:

public interface IQuacker {
  void Quack();
}

public class DuckFancier {
  public void GimmeADuck(IQuacker duck) {
    duck.Quack();
  }
}

new DuckFancier().GimmeADuck(new AVerySpecialDuck().AsImplementationOf<IQuacker>());

Yes, this is back to interfaces to give us discoverability, but with AsImplementationOf<T>, any class with Quack() could be turned into that interface without ever knowing about it. However, whether I satisfy that contract is still a runtime check.

Duck, Duck, go

What I really want is what go does:

type Quacker interface {
  Quack();
}

func GimmeADuck(Quacker duck) {
  duck.Quack();
}

I.e. true duck-typing. If the type provided satisfies the contract, it can get passed in and the compiler makes sure. Unfortunately, other than this feature go doesn't entice me all that much.

By trait or structure

A language that's a bit more after my own heart than go and has even greater flexibility in regards to duck-typing is scala. It actually provides two different approaches to this problem.

The first is a structural type, which uses function literals to define the contract for an expected argument type:

class AVerySpecialDuck {
  def quack() = println("Quack!")
}

object DuckFancier {
  type Quacker = { def quack() }
  def GimmeADuck(Quacker duck) = duck.quack()
}

DuckFancier.GimmeADuck(new AVerySpecialDuck())

A structural type is simply a contract of what methods a type should have in order to satisfy the constraints. In this case the singleton DuckFancier defines the structural type Quacker as any object with a quack method.

The second way we could have achieved this is with a trait bound to an instance:

class AVerySpecialDuck {
  def quack() = println("Quack!")
}

trait Quacker {
  def quack : Unit
}

object DuckFancier {
  def GimmeADuck(Quacker duck) = duck.quack()
}

val duck = new AVerySpecialDuck extends Quacker
DuckFancier.GimmeADuck(duck);

A trait is basically an Interface with an optional implementation (think Interface plus extension methods), but in this case I'm treating it purely as an interface to take advantage of scala's ability to attach a trait to an instance. This lets me add the contract on when used rather than at class definition.

Duck-typing in statically typed languages allows for syntax that can be concise and still statically verifiable and discoverable, avoiding the usual ceremony and coupling that inheritance models impose. It's a feature I hope more languages adopt and along with type inference is invalidating a lot of the usual gripes levied at static typing.