4
votes

I'm currently reading "The Well-Grounded Rubyist", and on page 196 I see the following:

Suppose you define a method at the top level:

def talk
  puts "Hello"
end

....

A method that you define at the top level is stored as a private instance method of the Object class. The previous code is equivalent to this:

class Object

  private

  def talk
    puts "Hello"
  end
end

...

To illustrate, let's extend the talk example. Here it is again, with some code that exercises it:

puts "Trying 'talk' with no receiver..."
talk
puts "Trying 'talk' with an explicit receiver..."
obj = Object.new
obj.talk

The first call to talk succeeds; the second fails with a fatal error, because it tries to call a private method with an explicit receiver.

I wanted to reproduce this on my local, so I put the above code in a Ruby file I created. I did indeed get the results mentioned in the book:

$ ruby talk.rb 
Trying 'talk' with no receiver...
Hello
Trying 'talk' with an explicit receiver...
Traceback (most recent call last):
talk.rb:22:in `<main>': private method `talk' called for #<Object:0x00007f9a8499c3e0> (NoMethodError)

I also tried the following, which produced the same error as running the code via the Ruby interpreter:

irb(main):008:0> load 'talk.rb'
Trying 'talk' with no receiver...
Hello
Trying 'talk' with an explicit receiver...
Traceback (most recent call last):
        4: from /Users/richiethomas/.rbenv/versions/2.5.3/bin/irb:11:in `<main>'
        3: from (irb):8
        2: from (irb):8:in `load'
        1: from talk.rb:22:in `<top (required)>'
NoMethodError (private method `talk' called for #<Object:0x00007ffb219c95e0>)

Next, I tried the same code in irb, and this time I got the following strange results:

irb(main):001:0> def talk
irb(main):002:1> puts "Hello"
irb(main):003:1> end
=> :talk
irb(main):004:0> puts "Trying 'talk' with no receiver..."
Trying 'talk' with no receiver...
=> nil
irb(main):005:0> talk
Hello
=> nil
irb(main):006:0> puts "Trying 'talk' with an explicit receiver..."
Trying 'talk' with an explicit receiver...
=> nil
irb(main):007:0> Object.new.talk
Hello
=> nil

As you can see, in the last code example, I was able to call Object.new.talk and have it print Hello as if .talk were a public method on the Object instance.

My question is- why is the talk method public on the Object class when I implement it directly in the REPL, but private when I implement it in a file and load it into the REPL (and also when I run that same file directly in my CLI via the Ruby interpreter)?

1
See this article: 'The IRB binds methods in the top level scope to main as public methods for convenience. btw, I think it would be clearer if you just referred to 'irb' rather than 'irb REPL'. - Cary Swoveland
@CarySwoveland- I edited my question for clarity. Also, after taking a look at the IRB source code, I see the following: Because irb evaluates input immediately after it is syntactically complete, the results may be slightly different than directly using Ruby.. This may refer to the public-level binding your article mentioned. - Richie Thomas
I think that refers to multi-line statements that ruby 'src.rb' has no problem with but irb can't handle. For example irb would choke on arr.map(&:to_i) on one line and .sum on the next (because it wouldn't know the first line continues). Here using irb you'd need to write arr.map(&:to). on the first line and ``sum` on the next line. - Cary Swoveland
That makes sense. Also, I found this link which seems to echo the point made in the article you linked. In any case, it seems like the key takeaway is that IRB doesn't "privatize" any methods declared in its top-level scope, the way MRI does. I'd be curious to learn why this is the case. - Richie Thomas

1 Answers

1
votes

Both irb and pry (sidenote: I strongly encourage to use the latter) tweak their input to declare all methods as public (during E stage of REP loop):

▶ def foo; end
#⇒ :foo
▶ public_methods.grep /foo/
#⇒ [:foo]

That’s it, no magic.


That is done mostly to simplify playing with it in scenarios when one defines the method here and then wants it to be accessible from e.g. there. In REPL it is worth it to make everything accessible everywhere.

def pretty_print; self.inspect; end
class A; ...; end
class B; ...; end

A.new.pretty_print
B.new.pretty_print

One should not pay too much attention to encapsulation, SRP, etc while playing the sandbox.


In general, it’s like declaring the module with generic helpers and including it everywhere for free.