Wednesday, October 26, 2011

Refactoring now possible for dynamic languages

There's an interesting post here from Bob Nystrom about getting used to the optional typing in Dart. But it did have one bit in particular that irked me.

"If it knows the type, then thanks to the previous point, it knows what you can do with it. Ta-da: auto-complete and refactoring are now possible for a dynamic language."

I don't want to pick on Bob, this seems to be one example of the widespread belief that you can't do refactoring in a dynamically typed language, despite the fact that much of the early work on it was done in Smalltalk. The term was actually coined with respect to Forth, as Brian Foote points out here, but it was popularized by the work of Bill Opdyke, John Brant and Don Roberts. The Brant and Roberts Refactoring Browser is currently the standard browser in VisualWorks and ObjectStudio and is the first example I know of automated refactoring support. Thanks to Don Roberts and Brian Foote who happen to be here at the Splash conference and so available to provide the historical information.

It's true that in a dynamic language you have a bit less information to use during refactoring. If we have a polymorphic message and we want to refactor it to, say, rename the method, but only some occurrences, then in a dynamic language we don't have a reliable way to know which senders refer to the ones we want to rename and which refer to the other implementations. So, if we wanted to rename MyClass>>printString we don't have a way to know reliably which senders of printString mean MyClass.

The problem is that we don't have a way to know that reliably in a statically typed language either. We will have more information that might be helpful in some circumstances. But suppose that we use a generic collection List. If we send any messages to the objects in that collection we don't know who the receiver is. So if we want to refactor, it's hard to make any assumptions about who that might be sent to.

Even with inheritance this sort of situation can arise. Suppose I want to rename the method printString in a subclass B whose superclass A also defines that method. If I find that message send to something whose static type is B, I can change the sender. But if if that message is sent to something whose static type is A, what do I do? The problem is that in the presence of multiple polymorphic implementations of the same method, renaming may not be a behaviour-preserving transformation.

I suspect that people using refactoring tools in statically typed languages don't notice these issues because in practice refactoring works fine for them in most normal circumstances. But the same thing is true for people using dynamically typed languages.

And in closing I'll add one comment from Don Roberts, that when he and John Brant looked at refactoring in Java they found that although the static types did give you some more information, the difficulty of satisfying the bookkeeping of the static type system ended up making it more difficult.