Paul Barry

Testing in a statically-typed language

December 19, 2008

One of the many “fringe” languages that has been on my radar for some time now is Haskell. Haskell has what looks to be a pretty good book available online and a local book club forming, which presents a great opportunity to study it. What is interesting to me about Haskell is that it is a statically-typed language, like Java, not a dynamically-typed language like Lisp, Ruby, Python, JavaScript, etc. I have really become fond of dynamic typing while programming in Ruby, but I always like studying languages that challenge your fundamental beliefs about programming.

Incidentally, this is one of the reasons I have been really interested in Clojure. It challenges the validity of object-oriented project in a very serious way. In Clojure, you don’t define new classes, instead your program is simply comprised of functions that takes input, possibly call other functions, and return values. Studying functional languages helps you to see the benefits and weaknesses of object-oriented programming. Read more about this idea of studying programming in Glenn Vanderburg’s article about Koans.

So anyway, back to Haskell, and the paradigm here that is worth studying is static typing versus dynamic typing. I must admit, after moving from Java (a statically-typed language) to Ruby (a dynamically-typed language), I’m currently a big fan of dynamically-typed languages. So I’m trying to keep an open mind in studying this, but this passage from Chapter 2 of Real World Haskell I have to object to:

Programs written in dynamically typed languages require large suites of tests to give some assurance that simple type errors cannot occur. Test suites cannot offer complete coverage: some common tasks, such as refactoring a program to make it more modular, can introduce new type errors that a test suite may not expose.

In Haskell, the compiler proves the absence of type errors for us: a Haskell program that compiles will not suffer from type errors when it runs. Refactoring is usually a matter of moving code around, then recompiling and tidying up a few times until the compiler gives us the “all clear”.

One thing I learned when moving from Java to Ruby is that developers often rely on the compiler as a false sense of security. The notion that you can refactor a bunch of code, get it to the point where it compiles and then feel that your code works is dangerous. You code can still contain all sorts of runtime and logic errors, therefore if you want to have any degree of certainty that your code is free of bugs, you need to have some sort of test suite, regardless of if you are using a dynamic and statically typed language.

I’ve also heard the argument made that statically-typed languages allow you to write less tests, but I have also not found this to be the case. Most syntax and type errors will be found with the same tests you use to test the logic of the program. In other words, you don’t have to write one suite of tests to check for syntax errors, one for type checking and another for testing the logic of your program.

So the moral of the story is that regardless of whether you are writing your code in a statically or dynamically typed language, you still have to TATFT.