Project

General

Profile

Actions

Feature #19300

closed

Move public methods from Kernel to Object

Added by zverok (Victor Shepelev) almost 2 years ago. Updated almost 2 years ago.

Status:
Rejected
Assignee:
-
Target version:
-
[ruby-core:111579]

Description

In my understanding, Kernel is a container for methods that are perceived as "global", and are available inside every objects as its private methods, like puts

class A
  def foo = puts "foo"
end

a = A.new
a.foo # prints "foo"
a.puts 'bar'
# private method `puts' called for #<A:0x00007f39a683db28> (NoMethodError)

There are, though, exactly three (if I am not missing something) methods that, according to documentation, break this intuition and belong to Kernel:

  • clone
  • tap
  • then

All those methods are public method for a receiver, and they mostly make sense with an explicit receiver. The most confusing of them is #clone, which is close cousin of #dup, but it is Kernel#clone and Object#dup.

This state of things is mostly harmless in practical code, but very inconvenient for teaching, reasoning about the meaning of modules and objects, and lookup for documentation.

But, in the description above, according to documentation is important statement. Because according to introspection, method definitions are spread differently:

puts "Public"
Object.new
  .then { |o| o.methods.group_by { o.method(_1).owner } }
  .each { puts "#{_1}: #{_2.sort.join(', ')}" }

puts
puts "Private:"
Object.new
  .then { |o| o.private_methods.group_by { o.method(_1).owner } }
  .each { puts "#{_1}: #{_2.sort.join(', ')}" }

Output:

Public
Kernel: !~, <=>, ===, class, clone, define_singleton_method, display, dup, enum_for, eql?, extend, freeze, frozen?, hash, inspect, instance_of?, instance_variable_defined?, instance_variable_get, instance_variable_set, instance_variables, is_a?, itself, kind_of?, method, methods, nil?, object_id, private_methods, protected_methods, public_method, public_methods, public_send, remove_instance_variable, respond_to?, send, singleton_class, singleton_method, singleton_methods, tap, then, to_enum, to_s, yield_self
BasicObject: !, !=, ==, __id__, __send__, equal?, instance_eval, instance_exec

Private:
Kernel: Array, Complex, Float, Hash, Integer, Rational, String, __callee__, __dir__, __method__, `, abort, at_exit, autoload, autoload?, binding, block_given?, caller, caller_locations, catch, eval, exec, exit, exit!, fail, fork, format, gem, gem_original_require, gets, global_variables, initialize_clone, initialize_copy, initialize_dup, iterator?, lambda, load, local_variables, loop, open, p, pp, print, printf, proc, putc, puts, raise, rand, readline, readlines, require, require_relative, respond_to_missing?, select, set_trace_func, sleep, spawn, sprintf, srand, syscall, system, test, throw, trace_var, trap, untrace_var, warn
BasicObject: initialize, method_missing, singleton_method_added, singleton_method_removed, singleton_method_undefined

E.g., internally, Object doesn't have any method defined, and is just a BasicObject with Kernel included.

So, there are three questions/proposals:

  1. Does this disposition has some internal sense, or it is more of a historical thing?
  2. Can it be changed so that public methods belonged to Object instead of Kernel?
  3. If the answer to (2) is "no", can at least docs for clone, tap and then be adjusted to follow other public methods in pretending they are Object's features?

Updated by hmdne (hmdne -) almost 2 years ago

In particular, Delegator works in an interesting way by:

  1. Inheriting from BasicObject
  2. Duping the Kernel module (as k)
  3. Undefining some methods from k
  4. Including k

So, for this particular purpose, moving methods from Kernel to Object may break compatibility.

See: https://github.com/ruby/delegate/blob/master/lib/delegate.rb#L44-L57

Updated by Eregon (Benoit Daloze) almost 2 years ago

My tip is never trust the docs regarding what method is where:

irb(main):001:0> self.method :clone
=> #<Method: Object(Kernel)#clone(freeze: ...) <internal:kernel>:47>
irb(main):002:0> self.method :dup
=> #<Method: Object(Kernel)#dup()>

(on ruby 3.1.3)

So it's an issue of RDoc. Probably makes more sense at https://github.com/ruby/rdoc.
I think we should not include Kernel methods at https://docs.ruby-lang.org/en/master/Object.html, Object and Kernel should be on separate pages because that is the truth.

Updated by nobu (Nobuyoshi Nakada) almost 2 years ago

  • Status changed from Open to Third Party's Issue

This is an issue of RDoc.
RDoc replaces rb_mKernel with rb_cObject (= Object) internally, but it seems not working in some cases (maybe in kernel.rb?).

There is no visibility differences between Kernel and Object.

Updated by zverok (Victor Shepelev) almost 2 years ago

@Eregon (Benoit Daloze)

My tip is never trust the docs regarding what method is where

Thanks, as a person that tries to maintain and update the docs for each version, I am well aware of it.

The initial ticket text also mentions this:

But, in the description above, according to documentation is important statement. Because according to introspection, method definitions are spread differently

So it's an issue of RDoc.

Well, I wouln't say it is an issue: it is a deliberate decision: Treat all public methods of Kernel as if they belong to Object.

Which seems to be handling that old discrepancy of "what is implied" (Kernel is for private methods that pretend to be "global", Object is for public methods, common for each object) and "how it is implemented" (all methods are belonging to Kernel).

Actually, the three problematic methods got to Kernels docs (skipping this RDoc trick) when they were extracted into kernel.rb. Oh, and looking closer, the same problem is related to #class and #frozen?.

If we'll look at 2.7 Kernel docs (before kernel.rb was introduced), everything was fine and public Object methods belonged to Object in docs.

Updated by Eregon (Benoit Daloze) almost 2 years ago

Yeah, and moving a method between C and Ruby like that is an implementation detail and should definitely not change on which page of the docs a method appears on.
Hence, it's 100% a bug of RDoc, it was a mistake to show Kernel methods on Object.html, I think now it's obviously clear.
Let's fix it.

(Alternatively one could add more hacks into RDoc to also treat those Kernel methods in kernel.rb as "Object methods", but that seems way too big a hack, isn't it?)

Updated by Eregon (Benoit Daloze) almost 2 years ago

To add on that, I have seen many many Rubyists confused due to RDoc showing Kernel methods on Object.html.
They thought all those methods are defined on Object and not on Kernel, but it's all a lie and confusion by RDoc.
So I guess the original intention was to simplify for beginners, but actually I think it hurts more than it helps.
Better fix it now than never.

Updated by zverok (Victor Shepelev) almost 2 years ago

@nobu (Nobuyoshi Nakada) OK, I discovered the same while answering to @Eregon (Benoit Daloze).

Do you believe the problem should be handled by

  1. adding more "edge cases" to RDoc (supporting kernel.rb)?
  2. removing edge cases from RDoc (all methods that pretended to belong to Object go to Kernel)?
  3. somehow adjusting docs structure in kernel.rb to "move" them to Object (I tried the simple Document-method: Object#clone trick, but it didn't work in Ruby sources)?..
  4. adjusting the code structure to better suit docs? (E.g. introducing at least object.rb and moving 5 mentioned methods there, and proceeding this way while probably extracting the rest in the future)?

TBH, I am most in favor of 4, as it seems to align the reality with the mental model that docs seem to always impose on the language users. But it is the most severe decision of the mentioned ones.

Hence, it's 100% a bug of RDoc, it was a mistake to show Kernel methods on Object.html, I think now it's obviously clear.
Let's fix it.

Please, let's not.
The mental model of "you look in Kernel to see 'global' methods available everywhere without a receiver, you look in Object to see generic methods every object has" is of tremendous significance, even if it doesn't correspond to reality.

Otherwise, it is incredibly confusing to see them in the same module: like, it will tell you, "every object has puts and class", because that's actually a lie in the meaning of usage.

If it is the only choice, I would rather add more hacks to RDoc to continue supporting the illusion.

The alternative is to (maybe gradually) shift the reality towards the useful illusion and separate "private 'global' methods" (of Kernel) and "public methods every object has" (of Object).

Better fix it now than never.

I agree with the intention, but not with the method :)

Updated by Eregon (Benoit Daloze) almost 2 years ago

The title is pretty confusing, but is a bit clearer with those questions:

zverok (Victor Shepelev) wrote:

So, there are three questions/proposals:

  1. Does this disposition has some internal sense, or it is more of a historical thing?

I believe it's all intended. So Object is not special and it's possible and meaningful to include Kernel (e.g., in some BasicObject subclass)
Private methods of Kernel are meant to be receiver-less methods, hence they are on Kernel for "everywhere" and not on Object which would mean the receiver matters.

  1. Can it be changed so that public methods belonged to Object instead of Kernel?

No, it would break Kernel.rand for instance.

  1. If the answer to (2) is "no", can at least docs for clone, tap and then be adjusted to follow other public methods in pretending they are Object's features?

I think it's time to fix this RDoc bug. But anyway, something to change in RDoc, not in Ruby.

Actions #9

Updated by Eregon (Benoit Daloze) almost 2 years ago

  • Subject changed from Move public objects from Kernel to Object to Move public methods from Kernel to Object

Updated by zverok (Victor Shepelev) almost 2 years ago

  • Status changed from Third Party's Issue to Rejected

The title is pretty confusing, but is a bit clearer with those questions:

Yeah, I actually started to write ticket thinking it is a small docs problem (as many of us, I frequently forget they all belong to Kernel), but then while trying to provide the full perspective, clarified what bothers me.

I actually think I'll restart it from scratch in dev-meeting-discussion-ready form :)

Updated by Eregon (Benoit Daloze) almost 2 years ago

zverok (Victor Shepelev) wrote in #note-7:

The mental model of "you look in Kernel to see 'global' methods available everywhere without a receiver, you look in Object to see generic methods every object has" is of tremendous significance, even if it doesn't correspond to reality.

I agree separating those somehow makes sense.
Although it could be done in the same page and just list all public and all private methods separately, isn't it?
The fact visibility is not shown properly at https://docs.ruby-lang.org/en/master/Kernel.html is part of the issue.
Like Public Instance Methods - #Array, that's wrong.

When I look at https://docs.ruby-lang.org/en/master/Kernel.html https://docs.ruby-lang.org/en/master/Object.html
I see a tremendous mess I would never be able to make sense of.
For instance the Kernel.html page has both frozen? and require in the same section, that's confusing, and I guess another case like you mentioned above.

I can see your POV now. So I'm not* against more hacks in RDoc to fix this inconsistency in the short term.
I do think pretending methods are in Object instead of Kernel hurts than it helps, but indeed, there also needs to be a clear split between public and private (with receiver/receives-less) instance methods of Kernel.

Otherwise, it is incredibly confusing to see them in the same module: like, it will tell you, "every object has puts and class", because that's actually a lie in the meaning of usage.

puts as private, class as public, that tells you whether you should use a receiver or not.

Updated by Eregon (Benoit Daloze) almost 2 years ago

I actually think I'll restart it from scratch in dev-meeting-discussion-ready form :)

IMHO this is purely an issue of RDoc, and so best to discuss it there or make a PR there.

Ruby methods will not have a different module/class to workaround this RDoc issue, I think that much is clear.
I'm pretty sure it's fine to add some special RDoc comment to kernel.rb so RDoc can recognize it (or maybe just use the filename or some command-line arg?).

IMHO there should not be an object.rb (since no methods are defined on Object), that would again leak some RDoc issue into Ruby.

Updated by zverok (Victor Shepelev) almost 2 years ago

IMHO this is purely an issue of RDoc, and so best to discuss it there or make a PR there.

I don't think so.

The question is "how the language should be documented", not "how the documentation tool (RDoc) should work".

The outcome of the discussion is of community significance (how new and growing Rubyists would understand some concepts), not of decorative significance (how some thing or other should render).

I might be the only person who considers it all that important, though.

Updated by mame (Yusuke Endoh) almost 2 years ago

Useless history talk. I once asked matz why Kernel exists. If I remember correctly, matz said the following.

  • In the beginning, there was a usage distinction of putting global function-like methods (exit, eval, etc.) in Kernel and method-like methods (clone, kind_of?, etc.) in Object.
  • However, this distinction was cumbersome to manage and had little advantage, so gradually everything was placed in Kernel.

In ruby 0.49 (1994/07/18), the earliest code available, we can see this distinction.

    rb_define_func(C_Kernel, "exit", Fexit, -2);
    rb_define_func(C_Kernel, "eval", Feval, 1);
...
    rb_define_method(C_Object, "is_kind_of", obj_is_kind_of, 1);
    rb_define_method(C_Object, "clone", Fobj_clone, 0);

However, there was already some confusion; Kernel#class and Kernel#hash were defined in Kernel. (Incidentally, a little interestingly, Kernel was a class, not a module, at that time.)

After that, it looks like matz did some design trial and error; a new class called Builtin was introduced between Kernel and Object, and every global function-like methods were moved to Builtin. However this was eventually reverted.

Then, everything was gradually moved to Kernel, as matz said. As of ruby 0.95 (1995/12/21), Object#clone moved to Kernel, and as of ruby 1.0-961125, Object#is_kind_of moved to Kernel (as Kernel#kind_of?). In ruby 1.0-971002, Object#inspect, which was maybe the last method defined directly under Object, was moved to Kernel.

I don't know why rdoc introduced the trick to replace rb_mKernel with rb_cObject, but the author (Dave Thomas) maybe remembered how Kernel used to be.

Updated by zverok (Victor Shepelev) almost 2 years ago

Thanks, @mame (Yusuke Endoh), it is pretty educative and exactly the answer I was looking for in my first ("historical") question!

Actions

Also available in: Atom PDF

Like0
Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0