Feature #19300
closedMove public methods from Kernel to Object
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:
- Does this disposition has some internal sense, or it is more of a historical thing?
- Can it be changed so that public methods belonged to
Object
instead ofKernel
? - If the answer to (2) is "no", can at least docs for
clone
,tap
andthen
be adjusted to follow other public methods in pretending they areObject
's features?
Updated by hmdne (hmdne -) almost 2 years ago
In particular, Delegator works in an interesting way by:
- Inheriting from BasicObject
- Duping the Kernel module (as k)
- Undefining some methods from k
- 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
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 Kernel
s 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
-
adding more "edge cases" to RDoc (supporting
kernel.rb
)? -
removing edge cases from RDoc (all methods that pretended to belong to
Object
go toKernel
)? - somehow adjusting docs structure in
kernel.rb
to "move" them toObject
(I tried the simpleDocument-method: Object#clone
trick, but it didn't work in Ruby sources)?.. - 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:
- 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.
- Can it be changed so that public methods belonged to
Object
instead ofKernel
?
No, it would break Kernel.rand
for instance.
- If the answer to (2) is "no", can at least docs for
clone
,tap
andthen
be adjusted to follow other public methods in pretending they areObject
's features?
I think it's time to fix this RDoc bug. But anyway, something to change in RDoc, not in Ruby.
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 inObject
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
andclass
", 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!