In his wonderfully sarcastic lightning talk, Wat, Gary Bernhardt explores the dark side of Ruby and Javascript with examples that seem to defy logic. When I first saw Wat, I immediately wanted to know more. How do these strange behaviors come about? Is it an interpreter error, designed into the language, or something else entirely? If the behavior is intentional, what is the reasoning behind it? I decided to take the plunge and find out for myself.
Let’s talk about Ruby
Undefined variable self assignment
Why would Ruby transform an undefined variable to nil
in self assignment? Let’s first breakdown a typical variable assignment in Ruby:
- Ruby initializes
foo
tonil
. - Ruby evaluates the expression on the right-hand side. In this case the result is simply
4
. - Ruby assigns the result of the right-hand side evaluation (
4
) tofoo
.
And now the same breakdown of a self assignment:
- Ruby initializes
foo
tonil
. - Ruby evaluates the expression on the right-hand side. To do this, Ruby looks up the value of
foo
, which by this point isnil
. - Ruby assigns the result of the right-hand side evaluation (
nil
) tofoo
.
The key point here is that Ruby will always initialize the variable on the left-hand side before evaluating the expression on the right-hand side. This guarantees that any undefined variables on the left-hand side will be defined by the time the right-hand side is evaluated.
We now know how Ruby allows for self assignment of an undefined variable, but that still leaves why. One reason this seemingly odd behavior exists is to accommodate a somewhat common idiom – conditional setting of variables:
(Note that the above is usually written @tasks ||= []
, though the two aren’t equivalent1.)
If @tasks
is undefined, and Ruby doesn’t initialize @tasks
before evaluating the right-hand side of the assignment, this idiom would not work as intended.
Ruby and bare words
This one is nothing more than an interesting application of language features, some more well known than others. The first and probably best known feature is that parenthesis are optional in Ruby method calls. Because of this, Ruby interprets ruby has no bare words
as if we wrote ruby( has( no( bare( words ) ) )
. Second, when making a method call, Ruby searches the current scope for a matching method definition. If it cannot find one, it passes the method name in question to method_missing
2. You can define your own method_missing
to provide extra runtime functionality. In this case, our method_missing
takes the passed in method name (plus zero or more other arguments) and returns a string of all those arguments joined together. Let’s break up the example to illustrate how the string is built:
Ruby behaves this way because part of its core functionality has been broken, making the why pretty pointless here. Note that in Ruby 1.9.3+, arguments are passed to method_missing
as symbols instead of strings, which causes infinite recursion. This is because Array#join
attempts to convert the symbols to string objects by calling to_str
, and since no to_str
method is defined for the Symbol
class, the method call gets passed to – you guessed it – method_missing
.
Let’s talk about Javascript
As it turns out, the Javascript portion of wat has already been covered by Adam Iley’s well written blog post. I discovered this post while researching my own, and was surprised to find it had not only already been written, but with an identical title! Thanks Adam!