When you’re a relatively new developer, it’s easy to get caught up in all of the things that you don’t know. And boy is that a long, long list of things. But there’s also another list that we should probably consider and revisit from time to time: the list of all the things we thought we knew, but didn’t really understand when we learned them.
Last week, while developing some curriculum for an Intro to Programming course, my co-teacher and I had one of those moments. We were trying to draw a diagram to explain the concept of an object “receiving” a method. As I looked back through our code snippets, I noticed that we were using very basic methods like puts
and gets
quite often, as most Ruby tutorials usually do. And then I realized something: I had no clue what the receiver of the puts
method was.
We both just sat there, partly perplexed and partly dumbfounded. How could we not know how puts
and gets
really worked? We used them all the time when we were learning to code, so perhaps we didn’t really think past the flexibility of these methods. But now that we are both more seasoned programmers, it seemed strange that we had never really thought about this before.
So, I did exactly what any good developer would do: I asked Twitter. Well, okay, I asked Twitter…and then I put on my detective hat and did some investigating of my own.
Puts, I love you, but you’re bringing me down
RUBYISTS, please help: what is the receiver of the puts and gets methods? Is it the main Object? I need to know, it's driving me crazy
— Vaidehi Joshi (@vaidehijoshi) May 8, 2015
I have a bone to pick with the puts
method. Well, okay, two bones I guess. First, it has no explicit receiver:
1 2 3 4 |
|
And second, it’s just REALLY hard to figure out what on earth is happening with this method. In fact, the hardest part about understanding puts
was figuring out exactly where inside of Ruby it lived.
Apparently though, there’s a method for that – well, two methods, actually. The owner
and the receiver
methods can be used to ask a method where it lives and who it can be called on. We can just pass it the name of the method we’re curious about as a symbol, and then ask it where it belongs:
1 2 3 4 5 |
|
WAT. It seemed like main
was the receiver of this method, while Kernel
was the owner, or the place where this method was actually defined.
But did that mean that main
was also self within the context of irb
? And what was main
, exactly? There was only one way to find out:
1 2 3 4 5 6 7 8 |
|
Pretty weird, right? But hang on…there was a line up there that was pretty new for me. Something about a Kernel
? Let’s do a bit more detective work.
Tell Me ‘Bout Them Kernels
Okay, you’re probably wondering: So that Kernel
thing – what’s the deal? Well, it turns out that Kernel
is a module, and if you’re like me, you probably haven’t thought about it that much until now.
But what you might remember about modules is that they have to be mixed into a class in order to be included; that is to say, in order for a class or instances of a class to have access to methods in a module, that module has to be included or extended into the class.
Which means that Kernel
has to be included into a Ruby class. But…which one? To figure out, we need to think back to what self
was within the context of irb
: main
. If we try to ask main
for its ancestors (so that we can deduce where the Kernel
module is coming into play), we get this error:
1 2 3 4 |
|
Okay, not so great. But hang on a second…since we know that main
is an instance of the Object
class, we can just ask the Object
class who its ancestors are!
1 2 |
|
Nice! We found Kernel
, and it looks like it’s included the Object
class. We can figure this out because it appears to the right of Object
, which means it has been mixed into the class to its left.
So, if a method like puts
is written in the Kernel
module, what can it be called on? Who is doing the “receiving” of this method call? Well, according to the Ruby docs, pretty much anything:
The Kernel module is included by class Object, so its methods are available in every Ruby object. These methods are called without a receiver and thus can be called in functional form.
So, theoretically-speaking, if the Kernel module is included by class Object, and is therefore available to every Ruby object, we should be able to call puts
on a String
instance, right?
1 2 3 4 |
|
Uh oh! Maybe we can’t. But…why not?
Global Functions
If you are smart cookie, you probably read that error and guessed that puts
is a private method defined on Kernel
. We can confirm this by using the private_method_defined?
method to double check:
1 2 |
|
The fact that puts
is a private method is important. It means that it can’t be called explicitly by anyone else. But how is it, then, that we use this method so frequently and easily? Well, because its mixed in to class Object
, it’s accessible to any Ruby object – but only in the current context. This explains why puts
never has to be called explicitly upon any object; instead, it will be called implicitly on whatever self
happens to be. In other words, the method will be called on the current context of self
.
It’s also pretty crucial that a method like puts
can be called in the current context of self. In fact, that’s exactly what allows us to do this within a Ruby class:
1 2 3 4 5 6 7 8 9 |
|
or something like this in irb
:
1 2 3 |
|
Even though the context of self
changes from the Duck Class, to an instance of a Duck, back to main
in irb
, we have access to puts
in all contexts. It’s kind of magical, if you think about it: a single private method, defined in a module, trickles down to all Ruby objects that descend from it!
And this magic has a name: global functions work. Since methods like puts
are defined on the Kernel
and mixed into the Object
class, they are accessible everywhere. They’re private methods, which means they must be invoked without an explicit receiver (also known as a “function form” of method invocation).
I really liked the way that this blog post explains the concept:
Virtually all Ruby objects inherit from Object and have access to the methods defined in the Kernel module, so Kernel’s private instance methods are accessible virtually anywhere in a Ruby program, regardless of self.
And you know what’s even cooler? These global functions defined on Kernel
are used everywhere! In fact, you probably don’t even realize that you use them. Here are some of the ones that surprised me:
gets
chomp
sleep
require
gem
rand
gsub
proc
eval
Yup. All of these methods are defined on Kernel
, available to all objects (including main
as self
in irb
), and don’t need a receiver to be called!
Now that you know where all these methods live and where they come from, you probably feel pretty powerful. You also will hopefully feel more purposeful when you write even a simple puts
statement in a line of code.
Perhaps the moral of the story here is to never take code for granted: always question how it works and try to understand exactly what’s going on in every line that you type.
The second moral is to use other developers on Twitter as resources to help you figure out what to Google, obvs.
tl;dr?
- The
Kernel
module is mixed into theObject
class, which means all methods (including private methods!) defined on this module are accesible to all Ruby objects, making them “global” functions. - Even though a method’s implicit receiver is
self
, the context ofself
changes depending on which class you are in; this is particularly important when dealing with “global” functions. - Curious about
main
andself
inirb
? Check out these two great blog posts.