Ruby aficionados swear by what they call "duck typing" and I figured, since I'd criticized C# in comparison with Ruby, it was only fair to return the favor.
Duck typing isn't new. Only the term is -- apparently coined in the last ten years, to describe one variant of dynamic typing. But the underlying principles have been around for a long time (it's Lisp's fiftieth anniversary this year).
But Ruby takes it to an extreme that is, to put it bluntly, unhealthy to quality code. The Pickaxe book (also known as Programming Ruby) has this to say (emphasis mine):
You'll have noticed that in Ruby we don't declare the types of variables or methods--everything is just some kind of object.and
. . .
If you've come to Ruby from a language such as C# or Java, where you're used to giving all your variables and methods a type, you may feel that Ruby is just too sloppy to use to write "real "applications.
It isn't.
. . .
[O]nce you use Ruby for a while, you['ll] realize that dynamically typed variables actually add to your productivity in many ways. (p. 365)
If you want to write your programs using the duck typing philosophy, you really only need to remember one thing: an object's type is determined by what it can do, not by its class. (p. 370)and it goes on to explain that all you need to do is call respond_to? to see if an object responds to the method you're calling.
. . .
You don't need to check the type of the arguments. If they support [the method you're calling], everything will just work. If they don't, your method will throw an exception anyway.
. . .
Now sometimes you may want more than this style of laissez-faire programming. ... (p. 371)
And therein lies the problem. I have seen Ruby code littered with calls to respond_to? Rails calls it about 200 times. To use Dave Thomas' term, it's sloppy.
Here's a typical use of respond_to?
def render(obj)Gee, wouldn't it be great if the language handled this automatically? What if we invented the idea of an interface, which you could use to declare a set of methods that you expect to be handled by an object? What if you could then specify that a variable or method parameter had to respond to the interface, with an appropriate type exception (uh, let's call it an interface exception) being thrown if the entire interface wasn't supported by the object? And what if we added the idea of method overloading, so that we could have alternate versions of methods that expect parameters that respond to different interfaces? And what if the compiler could check variables passed to methods to see if they support the requested interfaces and give you compile-time errors instead of runtime errors? Gee, C# does all of that.
case
when obj.respond_to?(:to_html)
return obj.to_html
when obj.respond_to(:to_json)
return json_to_html(obj.to_json)
when obj.respond_to(:to_s)
return html_encode(obj.to_s)
else
# Not realistic, since all objects respond to to_s
raise "can't render"
end
end
None of this is fundamentally incompatible with Ruby. The advantages to dynamic typing that are mentioned are, in fact, advantages. It is hugely useful to not have to specify the type of an object when it doesn't matter. But there is certainly a problem with not being able to specify what you expect in an object when it does matter. And it does, way too often.
There is nothing in the concept of dynamic typing or duck typing that requires that interfaces (or even classes) cannot be specified. But, instead, Ruby pushes work onto programmers that the compiler and runtime system ought to be doing automatically. In Ruby, it seems to me that duck typing is dynamic typing with blinders on. It results in bloated, sloppy, repetitive code. Personally, I'd much rather see code like this:
def render(IConvertsToHTML obj)The appropriate method can be decided dynamically at runtime -- the decision is not made at compile time. But, once inside the methods, support for the specified interface could be guaranteed. Also, notice that there is no need for an extra method to handle the unrealistic error case that I tossed in to make this point, though we could certainly add any number of additional methods for additional things we want to convert. It would be not be hard to invent a syntax for easy specification of interfaces as well as on-the-fly specification of required methods for a variable declaration.
return obj.to_html
end
def render(IConvertsToJSON arg)
return json_to_html(obj.to_json)
end
def render(IConvertsToString arg)
return html_encode(obj.to_s)
end
If the Ruby powers that be want it, Ruby can have all the advantages of dynamic typing and all the advantages of interfaces combined with great compiler support. What's the disadvantage?
Update 2/28: Changed example code.
Update 3/2: Follow-up posted.