Words and Code

One writer’s journey from words to code.

Investigating Ruby’s Global Functions + Kernel Module With Puts

#technicaltuesdays, ruby

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

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
♥ irb
2.2.0 :001 > puts "wat"
wat
 => nil 

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
2.2.0 :002 > method(:puts).receiver
 => main 

2.2.0 :003 > method(:puts).owner
 => Kernel 

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
2.2.0 :004 > method(:gets).receiver == self
 => true 

2.2.0 :005 > self
 => main 

2.2.0 :006 > self.class
 => Object

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
2.2.0 :007 > main.ancestors
NoMethodError: undefined method `ancestors' for main:Object
  from (irb):75
  from /usr/bin/irb:12:in `<main>'

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
2.2.0 :008 > Object.ancestors
 => [Object, Kernel, BasicObject] 

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
2.2.0 :009 > "".puts('wat')
NoMethodError: private method `puts' called for "":String
  from (irb):79
  from /usr/bin/irb:12:in `<main>'

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
2.2.0 :010 > Kernel.private_method_defined?(:puts)
 => true

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
Class Duck
  def self.swim
    puts "We're swimming."
  end

  def quack
    puts "Quack quack!"
  end
end

or something like this in irb:

1
2
3
2.2.0 :011 > puts "Waddle waddle"
Waddle waddle
 => nil

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 the Object 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 of self changes depending on which class you are in; this is particularly important when dealing with “global” functions.
  • Curious about main and self in irb? Check out these two great blog posts.