« The Cat Keeps Coming Back | Main | Simplifying, Serializing and Lifting »

Straw Man Against Static Typing

The paper, "Dyanimic vs. Static Typing—A Pattern-Based Analysis," by Pascal Costanza [linked from Lambda the Ultimate] describes three situations where static typing supposedly provokes the programmer to do more error-prone things than would dynamic typing. All three points are based on straw man arguments, although they elicit good lessons for language designers.

I'll take the three issues in turn. These are all in the context of Java.

Partial implementation of interfaces

This is, of course a common problem: while developing, you need to implement an interface and you want to start testing the methods you've written, without having to implement all the others.

Arguably, in this example, the interface to be implemented is overweening: it requires you to implement subSequence even though it could have a standard implementation that makes use of charAt. Mix-ins are an attractive solution to this kind of problem: a default implementation of subSequence can be given, that calls the subclass's charAt. A more efficient implementation can given when the programmer is ready.

But let's assume that we're given an interface with lots of methods, which are not at all expressible in terms of one another. That's the crux.

Dynamic checking allows you to avoid implementing something if you don't use it.

Static checking finds cases where you've not implemented something, since you don't know whether it will be used by some client code.

This is quite clearly a tradeoff. The paper asserts that dynamic languages "do the right thing" by default, but arguably dynamic languages are just as weak, since they don't give you a choice.

How about compiler settings for dynamicity? "Development mode" allows you to ignore partially-implemented interfaces, while "deployment mode" or "debugging mode" helps you catch them?

How about adding to static languages a shorthand for doing what the author suggests, namely declaring a particular method to to throw an exception?

public notYetImplemented subSequence(int, int);

This would, in effect, tell the compiler to shut up on this particular method, but forces the programmer to think about whether that's the right thing.

For even more brevity, the class declaration could say: "public class FileCharSequence partiallyImplements CharSequence" In this case, a compiler warning should probably warn the programmer that something is wrong.

Exceptions which should be ignored by a mid-level component

The scenario is: A uses B uses C. C will throw an exception. A cares about this exception; B does not. But to pass it on to A, B must be declared with these exceptions which are not "appropriate to its level of abstraction" in the words of the paper's author.

From the outset, there is a failure here in object-oriented design. If B makes direct use of C, it should be designed with C's characteristics in mind. An interesting case is one where B does not make direct use of C, but rather admits a pluggable component which it will call to do the low-level work. In this case, some work needs to be done to adapt the low-level package to fit into B's required interface. For example, an Adaptor class may be written which catches C's exceptions and converts them into exceptions which are declared within the needed interface. When composing reusable software components, this is often the case anyway: method names and signatures must be "rewired" with an Adaptor pattern.

However, an interesting language feature is suggested here. What if it were possible to use a brief "closure" to bind exception handlers around an external component? I have a third-party component called FooReader and my StatsPackage wants to read data from an interface like FooReader's; but FooReader may throw some ugly exceptions which StatsPackage is not prepared to handle. Rather than going out and writing a new Adaptor class, I would like to locally write a short wrapper which says "here's the object you need, but by the way, if this object throws exception X or Y, use this code to handle it." Imagine a construct bindExceptions, which modifies an object to add additional exception handlers--handlers that would be matched for any exception emanating from ANY methods of the object:

new StatsPackage(bindExceptions(fooReader)
               catch (UglyException e)
               { throw new PrettyException(e.description) }
               catch (Ugly2Exception e)
               { throw new PrettyException(e.name)});

Checking feature availability

This last argument is not a flaw of static typing, but rather an interesting quirk of Java which might beg a clever solution. The point here is that there is no atomic way in Java to check whether an object implements a certain method and dispatch to it. Obviously "synchronized" blocks are a decent answer to this need, but perhaps they impose undesirable concurrency properties.

It is true that in most statically-checked languages, we don't allow the kind of thing the author suggests--that is, to just invoke obj.method() and catch an exception if obj doesn't implement that method. But one could easily imagine a method-invocation operator in a statically-checked language which has the alternate signature: that is, instead of checking that the method exists, it could simply allow the invocation, but check that the block catches the MethodNotFound exception.

By contrast, the absolute principle of dynamic checking would argue that the compiler should not check that the exception is handled.

Sometimes static checks are too stringent; sometimes dynamic checks are too loose. The problem is not that one or the other principle is Correct; instead we are sometimes checking the wrong things at the wrong times. As language designers, our goal should be to improve the quality, expressivity, and timing of the checks that occur.

Post a comment