Can you say "qwak"? OK, then fly.

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:
    http://en.wikipedia.org/wiki/Duck_typing
  • Cedric Beust makes other interesting points on duck-typing, mainly around the idea that duck-typing hides the contract in the implementation:
    http://www.beust.com/weblog/archives/000269.html
  • A nice introduction to duck-typing for statically-typed language developers which puts the burden of type-checking on documentation and unit testing:
    http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/100511
    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.

7 Comments

  • I agree with you that if you don't buy into the whole unit testing thing then you are just hoping for the best with dynamic languages. But to say that unit testing is just there to do the job of the compiler (in statically typed world) is false statement I believe. In a statically typed world unit testing has many benefits too.

    Also, I believe that the ability to treat objects that exhibit the same characteristics in a uniform way is powerful. In a dynamically typed world I believe it is easier to utilize this, because you are not dependent on some object to implement some interface.

    For instance, if your daugther plays the role of a duck in a school play, she'll need to participate as a duck and answer to that interface if you will.
    Your daugther does not implement the duck interface because the designers didn't think of that and refactoring your daugther to implement that interface may not be an option. Still she's able to play the part of a duck in a school play. Things often exhibit dynamic properties that their designers hadn't thought of - which is what I like about dynamic languages.

    Cheers,
    Nis

  • Nis: I never said that. We're using TDD here and we completely buy into unit tests. I did not say that unit testing is just there to do the job of a compiler. Quite the reverse actually, I said that using unit tests to do the job of a compiler is a vain exercise.
    I think the crux of the discussion is that both duck typing and interface based programming are about contracts. The point is that duck typing is a very loose and unexplicit (when not completely hidden) contract. How and by whom this contract is fulfilled is irrelevant, it's the contract that matters.
    This blog post aims at finding a way to enable interface based contracts that work in a dynamic language and to understand why duck typing is considered such an important aspect of dynamic languages. We have a few ideas in the Atlas team about this but the feedback is important to us.

  • I've been using Ruby for a while now and, yes, there are problems. But the expressiveness of the language and the ability to design my code in a pure Object Oriented way eliminates my need for interfaces and static compilations. I think duck typing is an easy way to show how different dynamic languages are. Being able to take a string variable and make it an array cuts to the core of the language differences. I've found that I can wring my hands about the lack of X or the differences of Y, but to me the point is that once you give yourself to it you will code differently. And you will be a better programmer - in any language. And I happen to think that code will be better.

  • Mike: that's interesting feedback. I'm curious about the string thing: in .NET, a string pretty much behaves like a char array if you want it to. It has an integer indexer, you can foreach it, etc. So what more does Ruby buy you in this case?
    Also, how do you answer the points about duck-typing contracts being hidden and difficult to discover? What about explicit error messages when it fails? Do we care more about that because we are framework developers and have more users or is it a universal concern that app devs face too?

  • Hi Bertrand, sorry if I misread your statement about unittesting. I was probably skimming too fast :-) I see your point about contracts being important. It would be neat if you could somehow check if an object adheres to a certain interface (contract) at runtime without the object explicitly implementing that interface. It is often easy to do that manually, but I'm thinking more in terms of some language construct like "is_a" or just "is". Also, some languages (like Javascript) allows you to add methods to classes (and some also objects) so the contract might only be fullfilled at some point later during runtime. If you mixin a number of methods at runtime, it would be nice if these methods could carry with them an interface that belonged to that particular mixin.

  • Nis: that's an interesting idea (checking against an interface's members even if the interface itself was not declared). My thoughts were more leaning towards the second option of allowing for modifications to the type information of the object at mixin time. This way, one can be sure that the code that did the mixin actually intended on fulfilling the contract.

  • Bertrand, I'm working on a blog entry to give a more detailed response. But to address your second question about duck-typing contracts being difficult to discover I'm going to simply say that if you find yourself wanting to implement interfaces then your design is too complex and should be refactored. Dynamic languages allow you to simplify your domain more than system languages like Java and C#. What I need an interface to do in C# I can do simpler with open classes in Ruby. I hope to have a more concrete example in my blog entry.

Comments have been disabled for this content.