Hacker News new | past | comments | ask | show | jobs | submit login
There is no such thing as a global method in Ruby (zverok.space)
140 points by mpweiher 7 days ago | hide | past | favorite | 35 comments





This feels like a philosophical question at this point.

If the default object Kernel is always the same, then isn't it a global read variable? You can overwrite it, but it's globally accessible.

Also, the only true 'global' variables, are ones that you can access via Internet Protocol, everything you do in ruby, if you are not doing inter process communication, is going to be englobed by the process object.

Even if you write a variable to a file, it's still not 'global', it's encapsulated by the (physical) server object.

By reading or writing to a server through TCP to an open port public IP, the variable is then encapsulated only by the Earth object, so it's then and only then truly 'global'


I don’t really know Ruby, but I imagine that if a subclass overrides puts, then code in the base class calling puts will invoke that override instead of invoking the pseudo-global Kernel version. In other languages, the method definition in the subclass would be irrelevant, calling the global function wouldn’t cause polymorphic method dispatch on the current object.

Ruby has Refinements for a less-global way of doing this https://docs.ruby-lang.org/en/master/syntax/refinements_rdoc...

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.

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


It means that the default object (the one that receives the method call in the absence of a receiver) is not a global.

Won'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".


In 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.


For 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.


> 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


I 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.


I think example about `avatar_url` shows that it isn't a philosophical question.

its a take for sure, but it feels like it goes a bit off-topic. The OP article is about the idea that Ruby doesn’t really have global methods since methods in Kernel are scoped within each object. Arguing that only network-accessible variables are truly global posits more philosophical than practical, thus lacking utility for this context. Especially when some langs do indeed, in fact have global methods/state that's accessible from anywhere.

> This feels like a philosophical question at this point.

Kind of, but a) not all questions of semantics make for interesting philosophy, and b) the semantics in this case absolutely map to meaningful concerns regarding performance, behavior, and debugability/tactics for inspection of runtime environments. The fact that ruby doesn't have the concept of a function without a parent object whose method table is referenced by the caller complicates (for instance) passing around functions as values as well as whether or not you can expect the same function to even behave the same way when calling it twice.

Of course, much of this is hidden by syntax, and it certainly doesn't restrict you from acting as if it has proper functions rather than just methods, but it absolutely does matter if you want to understand how your code is running, particularly details like memory usage and garbage collection and whether or not ruby can inline code (spoiler: it generally can't).


I think anyone who uses Ruby regularly knows that `everything is an object` is the default, and that `Object` is the unstated parent for anything not specified. The rest falls from that.

> I think anyone who uses Ruby regularly knows that `everything is an object` is the defaul

“Default” implies that it is implicitly the case if no alternative is explicitly selected, but such alternatives exist. In Ruby “everything is an object” (or, at least, “every callable method is in a class and can only be called in context of an object”) is more immutable truth than default.

> and that `Object` is the unstated parent for anything not specified

Actually, an anonymous top-level instance of Object is the default object to which things apply; Object itself is an Object (and more specifically a Class), but it is not the default object, it is the class of the default object.


I hate those kinds of abstractions, ends up becoming as useless as it is all-encompassing.

Take linux "everything is a file", yes, your word document and your sound device are files, also how do you increase the volume of your speakers, er.... it's a file and you can write to it!

The usefulness of a system lies in the stances it takes, it takes guts to be opinionated. It's easier to create a system of stem cells that can be anything and can be configured to your needs, all you need to do is write a turing complete configuration file.


I don't think it's quite the same thing as "everything is a file". Unix files are typeless byte streams. Ruby objects have actual structure and type, and being able to do, for example, `49.times {print "this is a time!"}` because integers are objects doesn't add any friction to your life.

Objects and files are different, yes.

However one isn't inherently less abstract than the other.

For example, while objects may have 'structure and type'. They don't have an inherent byte representation, so one could argue they are even more abstract.


> also how do you increase the volume of your speakers

The mixer is a separate device.

> it's a file and you can write to it!

Which means you don't need any special software or tooling to use it. So you can automate actions with it using scripts in ways that any other desktop system won't do for you nearly as easily.

> all you need to do is write a turing complete configuration file.

Welcome to "general purpose" computing. Go back 50 years and see what the alternatives were. Or check out Plan 9 to see the file concept taken to a much better conclusion.


I think most experienced Ruby engs tend to think of code as existing within a runtime which is one global space with many nested (and parallel) execution states and rationalizing what's happening in static code means simulating that runtime in your head and navigating around it up, down and sideways.

This may seem like something all languages require but the way it works for me in Ruby is much different than when I am writing JS or Python. It's kind of hard to explain.


I think it's because which files code resides in doesn't ultimately have much bearing on the modularity, unlike in Python and JS (ESM).

Easier to understand if you come from Smalltalk

Who does these days?

I dabble in SmallTalk for hobby stuff, and it actually seems to be the other way around these days: People find SmallTalk after becoming enamored with Ruby. That's definitely my story.

It's how I found SmallTalk back in the original pickaxe days, and Lisp, and Perl. Pretty serious expansion of consciousness compared to Basic, Pascal & C++.

Similar. I found small talk from Objective-C and Ruby.

The people who are going to save us when all the newer generations know how to do is describe their desired outcome to a machine and get slop output.

I can't say smalltalk is popular even among this crowd.

I've been teaching a teenager how to code with smalltalk (Scratch): https://scratch.mit.edu/

or Objective-C, the Objective portion of which is also derived from SmallTalk

While I understand that objective-c has clear heritage from smalltalk, this comparison always struck me as farcical. Objective-C yields very few of the benefits of a proper Smalltalk VM outside of message passing, and i suppose some of the syntax. Meanwhile the burden of having to deal with C is absolutely staggering in comparison to the benefits.

The obvious benefit of C is speed and resource utilization. This was especially true on the late 80s.

Oh, absolutely—this wasn't meant as a value judgement. I've written my fair share of objective-c for this very reason (well, that and wanting to write native GUIs ages ago). I'm just saying the parallels between it and smalltalk are highly exaggerated.

Man I love this blog series



Consider applying for YC's W25 batch! Applications are open till Nov 12.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: