More thoughts on coding practice and ruby, following up on my last post.
The foundational analytical tool of the GoF book is to distinguish between type and class. Class is the implementation of an object, where in particular it’s logic is defined, it’s class hiearchy. Type is the behavior of the object, what messages/methods it responds to, what it does with them. These are two different things, two objects can share the same type will having completely different implementations or class hiearchy’s: classes.
The lesson of the GoF book is that you should always design to type not class. Code working with a given argument passed in can assume it has a certain type, but should never assume it has a certain class. This allows flexibility down the line to provide an object with a completely different implementation, but the same behavior.
The ruby communities idea of “duck typing” is similar. Don’t worry if it “is” a duck, if it quacks like one. But the common conception of “duck typing” kind of dispenses, at least conceptually, with the idea of type at all. Don’t worry about class, and don’t think about type either, just call and pray. This can lead to a certain sloppiness.
Design to Type
In the GoF conception, in order to design to a type, you have to know what the definition of that type is. If you expect you have a “duck” (not worrying about it’s implementation, but just that it behaves like type duck), what does a duck do exactly? Will a duck always quack? Will the great duck manufacturer in the sky decide that a duck no longer quacks in the future, and all these things you are expecting to quack stop quacking, and nobody told you?
In order to design to type, you have to have some idea of what defines a certain type. The traditional way to deal with this is with the “interface”, which is essentially a specification of a type. Something belonging to a certain interface (in Java, or in Objective C) could have any implementation at all, but you know it will implement certain methods. One shortcoming of interfaces is that they generally only define method signatures, not what those methods will actually do. Another problem that ruby types would catch upon is that it’s extra work to write and maintain an interface decleration/specification, it offends rubyists sense of “don’t repeat yourself”. There are various other conceivable ways to handle having a specification of type, either supported in language syntax or in developer practice, that would make different pro/con trade-offs.
The way (at least ideally, in theory) that the ruby/rails community deals with this is by unit tests. How do you know what a “duck” agrees to do? (Shades of “design by contract”). You can look at the unit tests. Whatever the tests for a duck test, that’s what you can count on a duck doing, regardless of how it’s implemented. You count on anyone giving you anything claiming that it’s a duck to have made sure that thing passes all of a duck’s unit tests.
This works reasonably well as a sort of ‘design by contract’, although it depends on developers to actually write unit tests effectively, and in a more-or-less self-documenting way. It would, in my opinion, help if developer’s realized the role of unit tests in a sort of “design by contract” and type-based programming.
Testing for type?
But sometimes you need to test a given object to see what type it is. Rails’ fondness for methods that can take various kinds of arguments, and do different things depending on what the argument is, is a case in point. (Is it a string or a hash or another sort of object? Do something different depending. ). How do you determine what “sort” of object it is? When we say “sort”, we’re really talking about type again: How does the object behave? In a language with interfaces, you can check the interface. In Ruby… a lot of code ends up calling the method kind_of?, which checks to see if an object or it’s ancestors are a particular class.
The problem here is that this is actually checking against implementation or class not behavior or type. It’s the very antithesis of design-to-type-not-class or duck typing! But what else can you do in ruby? You could enumerate every single method you expect the object to be if you want to treat it as a particular type, and test responds_to? to make sure it implements that method. But having to enumerate every method you expect (for something that acts like a given type of argument you are testing for), you start “repeating yourself”, listing things that are really defined elsewhere, and that have to be kept up to date when the elsewhere changes. To be even more ridiculously complete, you’d have to run some mini-unit-tests at runtime on receiving the argument, to see not only that it has the method signature, but that the method does what you want it to do. This is ridiculous or unworkable.
But what else can you do in ruby? So lots of code cheks against kind_of?. To deal with this, and make kind_of? work a bit better for type, developers could have objects that over-ride kind_of? to respond “true” to classes that actually aren’t part of their implementation, but that they behave (quack?) like. This could cause some confusion down the road though, becuase instead of distinguishing between type and class, we’re comingling them. Worse, there seems to be some code in Rails that isn’t even using kind_of?, as evidenced by my inability to get Rails render to treat an argument like a String, when it wasn’t actually a String, but behaved just like one and even responded with true to kind_of?(String).
No doubt rubyists will say that I’ve made up a problem that doesn’t actually exist, it’s just hypothetical, I’m not understanding the true profound nature of duck typing. Perhaps.