Thursday, November 02, 2006

Lame Duck Typing

"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!

1 comment:

Paddy3118 said...

Don't check if it has a method. Call the method as if it exists and deal with the exception if it does not.

- Paddy.