4 ms·
Not at all. Consider the following: class Foo def puts(*) Kernel.send(:puts, "Not what you expected") end def initialize
by semiquaver 2y ago
Not at all. Consider the following:
class Foo
def puts(*)
Kernel.send(:puts, "Not what you expected")
end
def initialize
puts "hello"
end
end
Foo.new #=> prints "Not what you expected"
The point the article makes is that the “default object” (self) is not always the same.
- Dylan16807 2y ago> Consider the following: That looks like variable shadowing? "global" doesn't mean "can't be shadowed". > The point the article makes is that the “default object” (self) is not always the same. I don't understand this sentence. Of course self isn't always the same. What does [“default object” (self)] mean?
- int_19h 2y agoIt means that the default object (the one that receives the method call in the absence of a receiver) is not a global.
- Dylan16807 2y agoWon't it go through your classes in order until it hits the top level object? Or something like that? I don't think you and semiquaver are using the term "default object" the same way TZubiri was. Either way, the ability to override doesn't mean the original value wasn't there. That original value was accessible from anywhere, and that sounds like it's pretty "global".
- ryandv 2y agoIn Ruby, all methods have a receiver. In the case where no receiver is explicitly specified, it is assumed to be `self` - thus line 7 of semiquaver's example desugars to `self.puts "hello"`. This is the "default object," referred to in Metaprogramming Ruby as the "implicit receiver." Since `self.class == Foo` at line 7, we must check that `self.class` (which is Foo) either defines an instance method `:puts`, or that some Module included in its ancestor list can `respond_to?(:puts)`: Foo.ancestors.map { |a| [a, a.is_a?(Class) ? a.instance_methods.include?(:puts) : a.respond_to?(:puts)] } => [[Foo, true], [Object, false], [Kernel, true], [BasicObject, false]] We find that `Foo.instance_methods.include?(:puts)`, and so that is the method that ends up being invoked: Foo.instance_method(:puts).source_location => ["(irb)", 2] # line 2 of semiquaver's example Consider now the class Bar: class Bar def initialize puts "hello" end end ... and notice that it does not contain any instance method `puts`: Bar.instance_method(:puts).source_location => nil Thus, unlike in the case of Foo, we need to traverse `Bar.ancestors` until we find a suitable definition for the method `:puts`: Bar.ancestors.map { |a| [a, a.is_a?(Class) ? a.instance_methods.include?(:puts) : a.respond_to?(:puts)] } => [[Bar, false], [Object, false], [Kernel, true], [BasicObject, false]] We find that Kernel responds to the method :puts, and so that will be the method invoked when we call `Bar.new`. We can patch Kernel to see what happens when we call Bar.new: module Kernel alias_method :orig_puts, :puts def puts(*args) orig_puts "Receiver #{self.inspect}" orig_puts(*args) end end Bar.new Receiver #<Bar:0x000000012006eae8> hello => #<Bar:0x000000012006eae8> Notice that `self`, the implicit receiver or "default object," is in fact an instance of the class `Bar`, even though the actual method invoked after going up the `Bar.ancestors` chain is on Kernel. Hence, there are no globals - every method is invoked on some `self`, explicit or implicit, and method lookup goes up the ancestor chain until you bottom out at Kernel or BasicObject. Note that Kernel is itself an Object. This is still an oversimplification. I recommend reading Metaprogramming Ruby and playing around in a REPL after reading the docs on Object, Class, Module, and BasicObject.
- Dylan16807 2y agoFor the sake of brevity, I'll skip an explanation for how I was interpreting that phrase. Instead, let me put it this way. The fact that "self" changes based on context is a very basic and obvious thing, and I'm pretty sure nobody suggested that "self" would always be the same thing. In particular, the first post in this comment chain was talking about Kernel being the same from anywhere (unless overridden). I would personally call any method that Kernel responds to a "global" method. Likewise for anything on BasicObject. I'm not worried about the receiver, I'm worried about access to the method.
- ryandv 2y ago> I would personally call any method that Kernel responds to a "global" method. But it's not really "global," because it's not actually accessible "everywhere," and so it's a poor choice of terminology. From the BasicObject docs [0]: BasicObject does not include Kernel (for methods like puts) Illustrated below: class Quux < BasicObject def initialize puts "hello" end end Quux.new # NoMethodError (undefined method `puts' for #<Quux:0x0000000134989768>) Better not to use shady terminology and to dive into the actual mechanisms and details behind how things actually work. Imprecise terminology leads to inaccurate mental models of what the system is actually doing. Though maybe you could argue that methods on `BasicObject` are "global" in a way. [0] https://ruby-doc.org/3.3.5/BasicObject.html https://ruby-doc.org/3.3.5/BasicObject.html
- TZubiri 2y agoI presume this means that there are scenarios where Kernel doesn't exist. In scenarios where Kernel exists, it is global. For example if running on an embedded system without a Kernel. The fact that the kernel is globally accessible to all objects is not a criticism of the language, you can't make up purity where it doesn't exist, as much as we may desire our programs are not ethereal math incantations that exist on Plato's world of ideals, and sometimes they depend on the Kernel which is the same for the whole process, (Duh). I didn't even get into CPU defined functions like addition, and assignment. How would you argue that + is not a global function? If you want to argue that it can be overloaded, recall that we speak of the default function in a read only capacity.