“If it walks like a duck and quacks like a duck, it must be a duck.”
While true, sometimes this must be combined with another epigram to arrive at what Ruby gives you:
“If it walks into a forest and quacks but there’s nobody there to hear it, is it a duck?”
The problem isn’t with duck typing, per se, but in testing the duckiness of an object. Consider a foo object that has a bar method, and a mumble object that also has a bar method. You can define a method that calls bar on an object that’s passed in:
def call_bar(object)
object.bar
end
and foo or mumble can be passed in with no ensuing calamity. And happily,
foo.respond_to? :bar
and
mumble.respond_to? :bar
return true as expected.
The real fun in Ruby starts when the bar method doesn’t exist in an object, but is created by the method_missing method dynamically. In this scenario, the introspection part of duck-typing can break down.
For instance:
class Fooble
def method_missing(m)
Fooble.class_eval "def #{m.id2name}() #{helper_for m} end"
self.instance_eval "#{m.id2name}"
end
end
fooble = Fooble.new
fooble.respond_to? :bar
returns false
,
call_bar(fooble)
calls fooble
’s bar
, which faults to method_missing, which creates the bar
method in Fooble
and returns whatever the code provided by helper_for(:bar)
returns, and then
fooble.respond_to? :bar
returns true
!
This is Lame-Duck Typing. Duck Typing that may not be effective all of the time.
Granted, this is a bit contrived, but in such scenarios testing has to be equally contrived. What it amounts to is a logical race condition. In systems that can create code at run time, introspection can be questionable.
Be careful!