adrift on a cosmic ocean

Writings on various topics (mostly technical) from Oliver Hookins and Angela Collins. We have lived in Berlin since 2009, have two kids, and have far too little time to really justify having a blog.

The Duck Always Bites Twice

Posted by Oliver on the 8th of May, 2012 in category Tech
Tagged with: rakerubywat

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'
  puts args[:glob]

What 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 =
=> #
=> 5
irb(main):009:0> = 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)


    def lookup(name)
      if @hash.has_key?(name)
      elsif ENV.has_key?(name.to_s)
      elsif ENV.has_key?(name.to_s.upcase)
      elsif @parent

To 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.

© 2010-2018 Oliver Hookins and Angela Collins