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.
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.
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] end raise InvalidHypervisorError end
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.8.7 :002 > 2[: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 hello => 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 ;)