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.