Nick KallenNick Kallen
Ruby Quiz (A Trick Question)
edit Posted by Nick Kallen on Sunday December 09, 2007 at 03:29AM

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)

Comments

  1. Tomtt Tomtt on December 09, 2007 at 04:11AM

    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? :)

  2. Dave Smith Dave Smith on December 09, 2007 at 04:12AM

    Hint: What does invoking "bar" yield before and after invoking "foo.bar"?

  3. Chris Anderson Chris Anderson on December 09, 2007 at 04:13AM

    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.

  4. Mark Wilden Mark Wilden on December 09, 2007 at 04:15AM

    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

  5. Chris Anderson Chris Anderson on December 09, 2007 at 04:16AM

    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.

  6. Tomtt Tomtt on December 09, 2007 at 04:39AM

    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:

    <code>Yo mama so fat she wakes up in sections!
    Yo mama so fat whenever she goes to the beach the tide comes in!
    Yo mama so fat she had to go to Sea World to get baptized
    Yo mama so fat when she sits around the house, she SITS AROUND THE HOUSE!
    Yo mama so fat when she sits around the house, she SITS AROUND THE HOUSE!
    </code>
  7. Tim Connor Tim Connor on December 09, 2007 at 04:52AM

    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)

    <code>def foo
      self.define_method :bar proc(1)
    end
    </code>
  8. Tim Connor Tim Connor on December 09, 2007 at 04:55AM

    damn, missed a comma as well.

  9. Garry Garry on December 09, 2007 at 07:49AM

    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 :)

  10. Garry Garry on December 09, 2007 at 07:49AM

    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 :)

  11. Garry Garry on December 09, 2007 at 07:49AM

    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 :)

  12. Garry Garry on December 09, 2007 at 07:49AM

    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 :)

  13. Garry Garry on December 09, 2007 at 07:50AM

    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 :)

  14. Garry Garry on December 09, 2007 at 07:50AM

    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 :)

  15. Garry Garry on December 09, 2007 at 07:50AM

    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 :)

  16. Garry Garry on December 09, 2007 at 07:50AM

    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 :)

  17. Garry Garry on December 09, 2007 at 07:50AM

    OH JESUS, sorry for the million posts, the "Submit" button wasn't doing anything for a while and I button mashed it!

  18. szeryf szeryf on December 09, 2007 at 10:13AM

    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.

  19. Milan Milan on December 12, 2007 at 07:09PM

    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.

Add a Comment (MarkDown available)