Duck Typing: The Duck Always Bites Twice
These days I’m noticing myself saying more and more frequently that Duck Typing is great, except when it’s not.
An amusing issue that briefly cropped up this afternoon was when we failed to correctly negotiate a data structure inside of a Rake task. Consider the following basic task:
desc "a test task"
task :test, :glob do |t,args|
if args[:glob].nil?
args[:glob] = 'some default value'
end
puts args[:glob]
endWhat kind of output would you expect would happen if you ran rake test right now? If you said nil you’d be right! That’s odd, I wonder what is going on here?
... puts args ...
Some debugging code later… what is the output? That’s right, it’s an empty hash – {}.
You could forgive us for thinking it might behave as one. Anyway, needless to say we then tried args.class and it turns out to be a Rake::TaskArguments, which evidently decides to make the arguments immutable but in such a way that you never know about it.
What usually happens?
$ irb irb(main):001:0> class Foo irb(main):002:1> attr_reader :bar irb(main):003:1> def initialize(value) irb(main):004:2> @bar = value irb(main):005:2> end irb(main):006:1> end => nil irb(main):007:0> f = Foo.new(5) => # irb(main):008:0> f.bar => 5 irb(main):009:0> f.bar = 6 NoMethodError: undefined method `bar=' for # from (irb):9
If you’ve seen the WAT video then you know what’s coming next:
def method_missing(sym, *args, &block)
lookup(sym.to_sym)
end
...
protected
def lookup(name)
if @hash.has_key?(name)
@hash[name]
elsif ENV.has_key?(name.to_s)
ENV[name.to_s]
elsif ENV.has_key?(name.to_s.upcase)
ENV[name.to_s.upcase]
elsif @parent
@parent.lookup(name)
end
endTo be fair, this is actually kinda cool. Not only can you do something like args.glob you can also do args[:pwd] or args.term or args.USERNAME.
Unfortunately it lets you do completely unexpected things as in the above example, which is handily translated into the symbol :[]= (which I like to call the Cookie Monster symbol), which doesn’t exist, returns nothing and throws away the value you attempted to assign to it. Because it is handled by method_missing, the additional value we supplied was accepted but not used, unlike any typical situation where it will cause a compile error.
(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)





