Ruby Quiz (A Trick Question)
Here is a little Ruby trivium for you.
Type this into IRB:
def foo
def bar
1
end
end
foo.bar
=> 1
Is this some magical lightweight object creation syntax so you can do cool method chaining? Let's try another example:
def foo
def foo
1
end
end
foo
=> nil
foo.foo
=> 1
So far so good. But now, type:
foo
=> 1
WTF? Is this a defect in Ruby?? Post your responses in the comments.
(Warning: this is a trick question)








My guess is that running foo overloads itself so that next time you run foo it runs the new def instead which returns 1. Do I get I prize? :)
remove
Hint: What does invoking "bar" yield before and after invoking "foo.bar"?
remove
In the first context you also get:
Array.bar #=> 1
bar is just defined on Object. I don't know what good foo is doing on the outside... or why it's return value changes after the deeper call. defect? I dunno. Useful... doubt it.
Ruby can be a little strange around the edges.
remove
Here's my go:
The "outer" foo definition is a method that, when invoked, defines a method foo that returns 1. The definition itself returns nil, so calling foo the first time returns nil. But that replaces foo with another definition, so calling foo the second time returns 1.
What I'm gathering from this is that methods defined at global scope can be called on nil, (or any object?)?
///ark
remove
Ah thanks Dave. bar doesn't get defined until foo runs. It makes some of the more verbose metaprogramming idioms look like overkill. I wonder if you can attach the inner method to an object. I'll have to try the whole thing in a class context sometime.
remove
You can build a silly iterate-then-repeat-last-value function using this principle as well:
<code>def fat_because def fat_because def fat_because def fat_because "when she sits around the house, she SITS AROUND THE HOUSE!" end "she had to go to Sea World to get baptized" end "whenever she goes to the beach the tide comes in!" end "she wakes up in sections!" end 5.times do puts "Yo mama so fat s" % fat_because end </code>Produces:
remove
With apologies to eminem: "And all ya fools act like ya forgot about self."
Ya, the foo methods just defines the bar method on self, when called, right? More verbosely it's roughly (not saying this syntax is correct, I suspect it might choke in rb, but for illustrative purposes)
remove
damn, missed a comma as well.
remove
Running 'foo' the first time runs 'def foo; 1 end', which now defines 'foo' to return 1. Running 'foo' at this point, without even doing 'foo.foo' will return 1. But why 'foo.foo' then also returns 1 is still a mystery to me. In fact, 'foo.foo.foo.foo.foo', ad infinitum, will still return 1. Interesting :)
remove
Running 'foo' the first time runs 'def foo; 1 end', which now defines 'foo' to return 1. Running 'foo' at this point, without even doing 'foo.foo' will return 1. But why 'foo.foo' then also returns 1 is still a mystery to me. In fact, 'foo.foo.foo.foo.foo', ad infinitum, will still return 1. Interesting :)
remove
Running 'foo' the first time runs 'def foo; 1 end', which now defines 'foo' to return 1. Running 'foo' at this point, without even doing 'foo.foo' will return 1. But why 'foo.foo' then also returns 1 is still a mystery to me. In fact, 'foo.foo.foo.foo.foo', ad infinitum, will still return 1. Interesting :)
remove
Running 'foo' the first time runs 'def foo; 1 end', which now defines 'foo' to return 1. Running 'foo' at this point, without even doing 'foo.foo' will return 1. But why 'foo.foo' then also returns 1 is still a mystery to me. In fact, 'foo.foo.foo.foo.foo', ad infinitum, will still return 1. Interesting :)
remove
Running 'foo' the first time runs 'def foo; 1 end', which now defines 'foo' to return 1. Running 'foo' at this point, without even doing 'foo.foo' will return 1. But why 'foo.foo' then also returns 1 is still a mystery to me. In fact, 'foo.foo.foo.foo.foo', ad infinitum, will still return 1. Interesting :)
remove
Running 'foo' the first time runs 'def foo; 1 end', which now defines 'foo' to return 1. Running 'foo' at this point, without even doing 'foo.foo' will return 1. But why 'foo.foo' then also returns 1 is still a mystery to me. In fact, 'foo.foo.foo.foo.foo', ad infinitum, will still return 1. Interesting :)
remove
Running 'foo' the first time runs 'def foo; 1 end', which now defines 'foo' to return 1. Running 'foo' at this point, without even doing 'foo.foo' will return 1. But why 'foo.foo' then also returns 1 is still a mystery to me. In fact, 'foo.foo.foo.foo.foo', ad infinitum, will still return 1. Interesting :)
remove
Running 'foo' the first time runs 'def foo; 1 end', which now defines 'foo' to return 1. Running 'foo' at this point, without even doing 'foo.foo' will return 1. But why 'foo.foo' then also returns 1 is still a mystery to me. In fact, 'foo.foo.foo.foo.foo', ad infinitum, will still return 1. Interesting :)
remove
OH JESUS, sorry for the million posts, the "Submit" button wasn't doing anything for a while and I button mashed it!
remove
Garry, that's because foo is defined on every object, including object 1:Fixnum, so foo.foo.foo.foo is just method chain. Each .foo is called on an object, that was returned from previous invocation. There's nothing magical in it. In fact you can chain this way every method from object, like nil? or to_s.
remove
In irb, self.class => Object, so that's why foo gets defined as an instance method of Object. When executed inside a class, it gets defined as an instance method for that class.
Oh, and Garry's multiple comments re: multiple function calls are sort of accidentally hilarious.
remove