From the topic of this and my last post, you would be excused if you think I have some weird fascination with ducks. In fact, I’m starting to question it myself.
I just find the topic of duck typing (and structural typing) to be interesting and I’m not known to miss an opportunity to beat a dead horse (or duck for that matter).
Often, when we talk about duck typing, code examples only ask if the object quacks. But shouldn’t it also ask if it quacks like a duck?
For example, here are some representative examples you might find if you Google the terms “duck typing”. The first example is Python.
def func(arg): if hasattr(arg, 'quack'): arg.quack() elif hasattr(arg, 'woof'): arg.woof()
Here’s an example in Ruby.
def func(arg) if arg.respond_to?(:quack) arg.quack end end
Does this miss half the point of duck typing?
In other words, don’t check whether it IS-a duck: check whether it QUACKS-like-a duck, WALKS-like-a duck, etc, etc,…
Note that this doesn’t just suggest that we check whether the object quacks and stop there, as these examples do. It suggests we should go further and ask if it quacks like a duck.
Most discussions of duck typing tend to focus on whether the object has methods that match a given name. I haven’t seen examples that also check the argument list. But yet another important aspect of a method is its return type. As far as I know, that’s not something you can test in advance with Ruby or Python.
Suppose we have the following client code.
def func(arg) return unless arg.respond_to?(:quack) sound = arg.quack /quack quack/.match(sound) end
And we have the following two classes.
class Duck def quack "quacketty quack quack" end end class Scientist def quack PseudoScience.new end end
Scientist certainly quacks, but it doesn’t quack like a duck.
func(Duck.new) // returns a MatchData object func(Scientist.new) // returns nil (match failed)
I think that’s one reason why the example in my previous post actually tries and call the method to ensure that the argument indeed quacks like a duck.
My guess is that in practice, conflicts like this where a method has the same name, but different type, is rare enough that Ruby and Python developers don’t worry about it too much.
Also, with such dynamic languages, it’s possible to monkey patch an object to conform to the implicit contract if it doesn’t match it exactly. Say if you have a
RobotDuck you got from another library you didn’t write and want to pass it in as a duck.
Thanks to GeekSam for reviewing my Ruby and Python idioms.