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
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
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
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!
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?
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:
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
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
1 2 3
Even though the context of
self changes from the Duck Class, to an instance of a Duck, back to
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:
Yup. All of these methods are defined on
Kernel, available to all objects (including
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.
Kernelmodule is mixed into the
Objectclass, 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 of
selfchanges depending on which class you are in; this is particularly important when dealing with “global” functions.
- Curious about
irb? Check out these two great blog posts.