Close

On explicit typing

…or, how to offend a lot of developers in a couple dozen paragraphs.  [original publish date: 27-Jun-2008]

Introduction

I have not been and will never be a fan of strictly type checked languages. They are too fussy; I’ve spent too much time trying to wrestle the type system into submission. Types, to put it plainly, are too often a pain in the ass.

I was reading yet another book on Ruby (well, what do you do for fun?), and came across this comment:

…since there’s no types, it’s really important to do more testing…

This coalesced something that I’ve been struggling to articulate for a long time. I’m equally mystified by people who dismiss typing as unnecessary (which I gather includes essentially the entire Ruby engineering population) and by people who insist that ubiquitous typing is essential to a properly functioning system (you know who you are). And here it is:

Types are testing.

By “types” I mean the whole bag of compile-time tricks, including required object declarations, explicit typing, declared interfaces for objects, etc. In each case the intent is to ensure that things actually take the form we expect them to take when they are passed around.

When you declare something should be a specific type, you are in essence asking the language environment to verify that the code is being used correctly. Obviously that is a form of testing, and if there’s one thing the last decade has brought to our attention, it’s that Testing is Good.

Types are documentation.

Which of these function signatures do you find more self-explanatory?

Ruby:

def complete?(config) { ... }

Hmm. Well, the “?” convention tells us it’s probably a boolean method, and we can guess that there’s some sort of configuration involved. Not a lot of information to go on, actually.

Java:

public boolean is_complete(RequestConfiguration config) throws IOException { ... }

I don’t even have to look at the implementation to have some idea of what’s going to happen there: we’re doing some sort of analysis of request processing, presumably a network request of some kind. If I need information about the config parameter, I can go look at the RequestConfiguration class/interface. Furthermore I know that I have to be ready to handle exceptions. It turns out there’s quite a lot of information conveyed by those extra characters. But those characters aren’t free; I’ve more or less tripled the amount of typing I have to do to create the function, and a lot of that has to be replicated carefully and completely over and over and over. Further, much of that information can sometimes be inferred from context, and where not possible, documented in a comment.

Typing should be possible.

Given the above code, let’s say I were to attempt the following Ruby:

obj.complete?({:some => 'hash value'})

We know from our Java example that the complete? method is in fact expecting an object of type RequestConfiguration; but in Ruby we won’t find out about that until run-time, and — since the definition of variables in practice often happens far from their use — figuring out where it happened, and what was expected, and so on, can be time consuming.

Wouldn’t it be neat if we could do that before we tried to use the code? Since Ruby is an interpreted language, there is no such thing as ‘compile time.’ But what if there were a way to run a test that did some level of verification that the things we were calling were getting the types of things they expect?

The Purists of the Cult of Ruby typically respond to the idea of “types” with pitchforks and torches. But they have quietly adopted a quasi-vocabulary for typing: they call it “duck typing.” See, if it quacks like a duck, it’s a duck — but whatever you do, do not suggest that is the same thing as typing. “Duck-typing” isn’t “types.” Um, ok. In Java, that very same idea is called an “interface”, and you can explicitly state that you want to operate on something that implements exactly that signature. People who like Java are often pretty enthusiastic about that stuff…

In my mind the only thing that really changes when you build a type system into a language is how thorough the consistency checking can be… and when that testing happens.

Typing should be optional.

In compiled languages, there is a very natural time to apply testing: when you compile the code. But since typing can be enforced at compile time, systems which allow it almost always insist on it. Have you ever suggested to a Purist of the Cult of Java that types can sometimes get in the way? Their reaction almost always includes lots of words and some color in the face.

Hmm.

If types are testing, and type enforcement is always good, doesn’t that imply that all testing is always good?

Let’s do a little thought experiment. Imagine with me, for a moment, a language which has a built in code-coverage detector which will not allow code to run unless every branch of the code is covered by tests and passes. That’s right, to run your system you have to have implemented 100% code coverage. Some of you, O imagined readers, are nodding your head right now; good idea, huh? You would know that your system would work, when you get it into production. That’s awesome!

I doubt it. The inevitable consequence of this would be a proliferation of tests that exercise code but do not actually care about the result, to the tune of object.do_something() ; assert true. Deadlines happen; if you can’t commit code without test coverage, smart clever people would figure out how to work around the artificial constraint that the language is imposing. More to the point, many skilled, professional developers sometimes opt not to test all their code even if there is no deadline pressure. Some code is simply not important enough to warrant the overhead of a full test suite.

Now we turn to the Purists of Test Driven Development, who will rant about studies showing that TDD slows development but saves a lot of money. Indeed, the evidence is mounting that code with a well designed and thorough test suite will cost less to maintain in the long term than code without. I don’t want to dismiss that finding; it’s very significant, and I wish more developers (and their managers) were aware of it. But it is important only if the code in question will ever actually require support! A lot of code does not: dirt simple code, experimental code, one-off scripts, etc. So our imaginary language would probably need a simple way to indicate “don’t bother testing this section of code” — which really isn’t much different than what we have today, with test engines that report code coverage.

So why are types different?

require ‘brains’

The goal of language is clarity and simplicity. You shouldn’t have to think hard about how to express your ideas; you should be focused solely on how to express them clearly.

I have never found a strictly type-checked language for which the types didn’t often get in the way. That these languages always allow for type coercion is a big hint that enforced type-checking is flawed, and C++ templates and Java generics are clear attempts to work around a shortcoming in the language. I know for sure that I work more slowly in strictly type-checked languages, and there is no appreciable difference in code quality.

The goal of language is correctness. You shouldn’t have to work hard to figure out how to test your ideas; you should be focused on how to prevent things from going wrong.

I quite like Ruby. However, I will never be comfortable in a language which doesn’t allow me to define interfaces (when I want to), declare exceptions (when I choose to), can optionally be configured to require all variables to at least be declared, and allow me to define types for method parameters such that when the types are violated an error occurs immediately.

Typing has its place alongside rigorous testing and solid documentation; but like testing and documentation, typing should be used judiciously. Why is this such an ideological debate?

About dondo

1 thought on “On explicit typing

  1. “I quite like Ruby. However, I will never be comfortable in a language which doesn’t allow me to define interfaces (when I want to), declare exceptions (when I choose to), can optionally be configured to require all variables to at least be declared, and allow me to define types for method parameters such that when the types are violated an error occurs immediately.”

    You may want to take a look at https://sorbet.org/

Leave a Reply

Your email address will not be published. Required fields are marked *

Are you a spambot? *