4

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

Richie Thomas
  • 3,073
  • 4
  • 32
  • 55
  • 3
    See [this article](https://rubyplus.com/cheatsheets/411-ruby-top-level-methods): '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 Dec 31 '18 at 05:44
  • @CarySwoveland- I edited my question for clarity. Also, after taking a look at [the IRB source code](https://github.com/ruby/irb/blob/711c4e69495a69008d1be9714d65a42f8c15bf58/lib/irb.rb#L221), 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 Dec 31 '18 at 06:13
  • 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 Dec 31 '18 at 06:25
  • That makes sense. Also, I found [this link](https://www.ruby-forum.com/t/differences-between-irb-and-ruby/96727/6) 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 Dec 31 '18 at 06:34

1 Answers1

1

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.

Aleksei Matiushkin
  • 119,336
  • 10
  • 100
  • 160
  • I added following in irb, `class Object; puts self; def print1; end; end;` & `puts self; def print2; end;` both define instance methods for `Object`, but checking self in both scenario, one shows class `Object` and another shows Object class instance `main`. Please clarify if I missed something. – ray Dec 31 '18 at 06:46
  • `main` is an implicitly defined by IRB instance of `Object`. – Aleksei Matiushkin Dec 31 '18 at 06:49
  • If one added `private :pretty_print` the method would indeed become a private instance method of `Object`, so it could still be used at top level. It could not then be used in other classes, but I don't know why one would want to do that in irb. That is, why `A.new.pretty_print`? Perhaps you could elaborate on why its a "convenience" for methods defined in `main` to be public. – Cary Swoveland Dec 31 '18 at 07:03
  • @CarySwoveland because you do not need to bother with defining a module for the common behaviour and including this module everywhere. – Aleksei Matiushkin Dec 31 '18 at 07:04
  • True, but that's not a great saving. I would have created the module for inclusion in the classes (in irb), in part because I would want the code to work when it's executed in a file. I've never used irb in the way you mentioned. (Have you?) Maybe I should consider doing so. – Cary Swoveland Dec 31 '18 at 07:08
  • @CarySwoveland “I've never used irb in the way you mentioned”—me neither :) But still, it was done most likely with this use-case in mind. – Aleksei Matiushkin Dec 31 '18 at 07:11
  • I think you're right as to "why", but if so I'm not sure I agree with the design decision (but who am I?). – Cary Swoveland Dec 31 '18 at 07:14
  • Related question- is it equally easy for the interpreter to determine top-level scoping when running a program from a file, vs interpreting commands line-by-line in a REPL? I was thinking that if it's more difficult to determine whether the execution flow of a REPL is in top-level scope at any given time, it would also be harder to determine when to add those top-level methods as private methods to the Object class, and that maybe this influenced the above design decision. – Richie Thomas Dec 31 '18 at 18:04
  • To add them as private does not require any additional action taken. Making them public does. – Aleksei Matiushkin Dec 31 '18 at 18:26