16 February 2006

Interfaces and duck typing

It's nice to be able to add interfaces to a class with minimum effort.

In Java, you have to add the implements tag at the top, copy all the methods in, and implement them all. There's nothing terribly wrong with this.

In Ruby or Python, there are no explicit interfaces, and the result when you make a mistake is AttributeErrors at run time. One advantage is, you don't have to "pretend"-implement interface methods that you don't actually support. For example, Python sockets mostly resemble files, but they don't have a seek() method.

The thing is, you can never really tell if an object provides an interface or not. Static analysis is hopeless because the interface isn't defined anywhere. Runtime analysis can't work because there's no tag on your object, nothing to check. You can use hasattr(f, 'read'), but even then there's no guarantee that the caller intended it to work like file.read().

Some Python people say you ought to just try the operation, and if it fails, cope with the error. I think this is ridiculous.

Here's an alternative syntax, somewhere in between Java and Python.

package std;

interface bytes read(int len);
interface bool isAtEnd();
interface long tell();
interface boid seek(long offset, SeekDir whence);
interface void close();
Here I'm defining interfaces. Each one is a single method. Now suppose I have a spam generator. I can add methods that make it act kind of like a file.
class Spammer {
    ...
    std.read(len) { return generateSpam(len); }
    std.isAtEnd() { return false; }
    std.close() {}
}

The spammer supports read() and isAtEnd() (always false, because there's always more spam). close() is harmless enough. But code that uses seek() isn't going to work with a Spammer, which I think is fine.

The key here is that by naming the method std.read, the author is indicating (with a minimum of typing) that the class implements a specific, documented interface. In introspectable languages (like Java and Python) this would be easily queryable at run time.

(Incidentally, a statically typed language can infer the return type and parameter types, so it's actually less typing to define std.read(len) than bytes read(int len). Not sure if this is actually a good idea. But it's trivially possible.)

No comments: