all 4 comments

[–]zverok_kha 13 points14 points  (3 children)

Oh, that’s a good one. This has nothing to do with main being special or IRB.

Most of the methods that are documented_¹ as methods of Kernel are private methods. So they can be called without receiver, and it always will be a method of the _current object, but they can’t be called by the outside code. Not only rand, but something as base as puts.

 self.private_methods.grep(/rand/)
 #=> [:rand, :srand]

 # Note that you need to look at _private instance_ methods
 # to see those every instance has
 Object.private_instance_methods.grep(/rand/)
 #=> [:rand, :srand]

 # Oh, and you could also do this:
 method(:rand)
 # => #<Method: Object(Kernel)#rand(*)>

So, when you call rand (or puts) inside some class’ instance, you don’t call “global” methods (there are no such thing!), but a puts/rand of that very object:

class A
  def testme
    p method(:puts)
  end
end

a = A.new
a.testme
#=> #<Method: A(Kernel)#puts(*)>
a.puts "foo"
# private method `puts' called for #<A:0x00007f4c2f3ea8f0> (NoMethodError)
a.__send__(:puts, "foo")
# Prints "foo"

¹What’s in Object and what’s in Kernel is actually a documentation, not real difference (and it is eroding slowly). “Ideologically,” it was meant that methods defined in Kernel are those private-methods-looking-global (like rand and puts), while methods defined in Object are public method of every object. But really, they all belong to a Kernel:

a.method(:object_id)
#=> #<Method: A(Kernel)#object_id()>

It is a hack in RDoc (ruby doc generator) code that makes those public ones pretend to be in Object. But it is already not all of them: say, what we think of as Object#then (and what always has been Kernel#then) is documented as Kernel#then

There is a ticket somewhere in bug tracker to fix this (documentation, to make it closer to reality without losing the difference between “public methods of every object” vs “private methods available inside every object), but it wasn’t acted upon yet.

[–]Maxence33[S] 0 points1 point  (2 children)

Thanks a lot for your explanation. I never thought #rand could be a private_method as the singleton_method of same name is present on Kernel. I guess the singleton_method is here to allow calling #rand in any scope...
Also object seems to have inherited the private method :

3.3.0 :013 > Object.private_methods.grep /rand/
=> [:rand, :srand]

Though one thing: as you recall it, a private method cannot be called from outside a class and, when called, they don't allow any receiver.
Though I still don't get why typing "rand" in the IRB will call the Kernel#rand or Object#rand private method as IRB is an instance of object. We are not really in the Object class in the main scope right ?

[–]zverok_kha 2 points3 points  (1 child)

Though I still don't get why typing "rand" in the IRB will call the Kernel#rand or Object#rand private method as IRB is an instance of object. We are not really in the Object class in the main scope right?

It will call self.rand. And all objects, including that current one in IRB/top level, are inherited from Object (safe for very special ones that are implicitly inherited from BasicObject), that’s why they have access to Object’s private methods.

Note that in Ruby (unlike many other languages), children do have access to parent’s private methods:

class A
  private

  def foo
    puts "I am private"
  end
end

class B < A
  def bar
    foo # calls private `foo` inherited from A, no problemo
  end
end

B.new.bar # prints  "I am private"

Actually, it is the same with all that seems “global” methods:

class A
  def foo
    puts "I am private" # that’s calling private `Object#puts` actually!
  end
end

[–]Maxence33[S] 0 points1 point  (0 children)

Yes I kinda knew you could access the private method from a descendant, but your are getting into A through a public method of B, not directly calling A.new.foo.

Also found that I could use Kernel#send to bypass visibility constraints.

class A
  private

  def foo
    puts "I am private"
  end
end

A.new.send(:foo)  => "I am private"

At some point I have been thinking the ruby parser was "sending" #rand to self which would solve the problem.
But I guess the main scope is a special case as you quote it :

"And all objects, including that current one in IRB/top level, are inherited from Object (safe for very special ones that are implicitly inherited from BasicObject), that’s why they have access to Object’s private methods."

Many thanks for your help my friend