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.


Posted by Oliver on the 1st of February, 2012 in category Tech
Tagged with: rubywat

I've been spreading this excellent talk by Gary Bernhardt around my co-workers and friends who universally love it. I'm also proud to say the "WAT" meme has (hopefully permanently) entered my team's culture as we find it adequately sums up our feelings towards various bits of software that we either have to work with or maintain.

Half my team is away on business trips or sick leave at the moment so it was a relatively quiet day, which the rest of us spent squashing bugs. Unfortunately for us, most of our codebase is in Ruby and we have started cultivating a reasonable collection of Javascript in some of our web-oriented interfaces so you can imagine that Gary's talk was particularly poignant.

A co-worker had submitted a review request this afternoon (yes, code reviews are awesome) and I was talking a brief look through it as I was not that familiar with this piece of code. He'd admirably refactored some quite nasty stuff but I was a bit perplexed as to some of the logic. An example might be similar to this:

def check_vm_state(vm, vmstate)
  if vmstate[vm][:host]
    return vmstate[vm][:host]
  raise InvalidHypervisorError

OK, it wasn't nearly as bad as this but you get the point. We're meant to pass in some kind of hash which contains status information, pull out something relevant to the VM in question and return it; if not, throw an error. What threw me was the test for this which was clearly just some fudged parameters but I couldn't figure out what was going on:

assert_equals some_valid_value check_vm_state(1,2)

Again, for your sanity's sake it was a little more than this. I wondered how this could possibly work, and my co-worker did the same. "But clearly the tests are passing!" someone exclaimed. Let's take a look:

1.8.7 :001 > 2[1]
 => 1 
1.8.7 :002 > 2[1][:host]
 => 0 


Apparently the [] operator on a Fixnum will retrieve the binary digit value at that index. OK, not too unreasonable. But what about the index off the :host symbol?!? Well, easy - it is cast to its unique identifier which is an integer value - in this case 16473 but this changes every time you run Ruby. This bit index clearly can't exist for the number 2 so the return value is 0. WAT.

Here's another cool one. We had some awful code that was a bunch of if statements testing for equality to various things - which in most instances you would just replace with a case/switch statement and be done with it. So we tried, and failed (initially):

> foo = 'hello'
 => "hello"
> case foo
>   when 'hello'
>   puts 'hello'
>   when 'goodbye'
>   puts 'goodbye'
>   else
>     puts 'something else'
>   end
 => nil 

Absolutely no surprises there. Except in this case we're more interested in the type of the object we are dealing with since our particular piece of code is making use of Ruby's duck-typing to do some smart manipulation of various objects in similar ways. So now:

> foo.class
 => String 
> case foo.class
>   when String
>   puts 'string'
>   when Fixnum
>   puts 'fixnum'
>   else
>     puts 'something else'
>   end
something else
 => nil 

Er... WAT? Obviously we failed to take into account that the case statement calls the === method on the operand in each when statement and it behaves completely differently depending on whether it is used with an Object or a Module (which Class inherits from as do String, Fixnum etc). For Module it will only return true if the thing being compared to is an instance or descendent of the thing being operated on, whereas Objects just compare equality (and not identity).

I'm sure this behaviour is also overridden in other types to "make sense" under the circumstances, but unfortunately just serves to confuse by its inconsistency. I realise this represents something like a raised middle finger to dyed-in-the-wool Rubyists but it really isn't following any principle of least surprise that I know about.

Anyway, after all of us said WAT about nine-thousand times I came to the conclusion that duck-typing is only cool if not every single basic type in the language responds to most method calls, usually in completely different and unexpected ways. If it swims like a duck, looks like a duck and quacks like a duck, it could be a duck. Or some kind of shapeshifting, organism impersonating cyborg warrior from the future intent on the destruction of our minds and all we hold dear to us.

On a slightly less inflammatory note, Ruby is actually still quite nice to use once you know all it's little quirks. I went to my first Clojure meetup in Berlin tonight and was introduced to some pretty awesome concepts. I'm not sure I'm completely sold but it may actually be time to broaden my horizons somewhat but I guess I'm stuck with Ruby for a while yet ;)

© 2010-2018 Oliver Hookins and Angela Collins