This morning, I was at a language symposium that's taking place on the Microsoft campus. Many language gurus are there, Anders is doing the keynote, so it's definitely a place where you can learn a thing or two. Many of the talks are about dynamic languages and how to implement them in the CLR, and as usual, you can see the usual duck-typing quote on all slide decks:
"If it walks like a duck and quacks like a duck, it must be a duck."
Really, folks, I don't know about that. While I generally like dynamic languages and the trend they represent, I've always been extremely uneasy about that, and what's more I don't think it's a necessary outcome of using a dynamic language.
First, we don't know that it walks "like a duck" and quacks "like a duck". We know that it walks because it has a "walk" method, and we know that it quacks because it has a "quack" method. But nothing tells us that it actually walks like a duck. My daughter got a walk method two years ago, and she has a quack method that she uses all the time. You can ask her to walk and you can ask her to quack, but that doesn't make her a duck (no matter how hard she likes to pretend she really is).
Look at the walk method. I can ask a duck to walk, yes, but I can also have a walk method on a tree structure. One will make the object change its position, the other wil enumerate all the nodes in the tree. Same name, very different behavior. And given that most dynamic languages don't even enable you to look at/check the list of arguments, their types, and the type of the return value (like, say, a .NET delegate lets you), there's no way you can tell.
What you need is some kind of a contract that's more rigorous than just a name. Wait a minute, isn't that what an interface is supposed to be? Sure is. That's why we added interfaces to Atlas (which also brings the nice benefit that one operation is enough to verify the whole contract, you don't need to check each and every method).
Of course, if you've been using a dynamic language lately, you'll answer that yes, there are problems, but they are outweighted by the increased productivity of dynamic typing. Sure, that may be true in the prototyping phase, but as soon as your project is going to reach a critical size, it's going to become increasingly difficult to maintain. Testing, I can hear dynamic language fans say, will alleviate this problem. No, it won't because that means that I'll need to write tests for stuff that a compiler is supposed to check. Tests that are not only boring to write but that will also nullify the benefit of saving a few characters by not giving the type of my parameters in the first place. Furthermore, compilers are very complex pieces of code and coming up with equivalent tests is extremely difficult if not impossible. There's always the possibility of doing the type-checking at runtime (which is what we're doing in Atlas debug builds) but that's both expensive in terms of performance and only partially valid when compared to compiler-validation because you can't check every possible case, you only check what the consumer code is giving you to check. That's why you see things like Script# emerging...
Finally, I'd like to point out that it's not because a language is dynamic that it can't be strongly-typed. The concept of a contract that the class and its consumers both know and agree on is orthogonal to the concept of dynamically adding to a type. And there is the Object type to deal with the completely unknown in the very rare cases where that's necessary.
I'd like to hear your thoughts and reactions about that.
Further reading on this subject:
- Duck typing on Wikipedia:
- Cedric Beust makes other interesting points on duck-typing, mainly around the idea that duck-typing hides the contract in the implementation:
- A nice introduction to duck-typing for statically-typed language developers which puts the burden of type-checking on documentation and unit testing:
Interesting quote: "I have found that once I stopped assuming that the callers of my method [...] are stupid and don't know how to read my documentation [...] then writing in Ruby became a whole lot more natural and somewhat less verbose". Well, natural and less verbose certainly, but your callers may not be stupid and still have bugs in their code. If your code just crashes at some random place with a cryptic error message because it's expecting a duck and got a dukc, the more explicit an error message you give the better. At least that's what our users are telling us.