Demystifying Ruby functions

(NOTE: Edited slightly on 6/6/2015 for better clarity about the behavior of Object in different versions of Ruby. This post mostly applies to version 1.9.)

Something that confused me about Ruby at first was why tutorials, articles, and books kept referring to Ruby “methods” when talking about functions. For example:

def hello
 puts "hi there"
end

Since hello is not being defined as part of a class or module, it stands on its own. Any reasonable programmer would call this a function, not a method. Initially, I took this as some odd quirk of the Ruby community. They include some pretty strange folks, after all.

Turns out there’s a good reason for this terminology. It IS a method.

As Pat Shaughnessy explains in this article, when you execute def hello at the top level, it gets added as an instance method to the Object class. You can verify this yourself:

def hello
 puts "hi there"
end
# this will print 'true'
puts Object.respond_to? :hello

When you call hello, the method is invoked on a receiver (ie. self) that is an object instance called “main”, whose class is Object.

def hello
  puts "hi there"
  puts "self is #{self}, its class is #{self.class}"
end
# this prints 'self is main, its class is Object'
hello

This explains why defining methods at the top level appears to make them globally accessible. What actually happens is that, when you call hello from any class or module, Ruby will look for it up the class hierarchy until it finds the method in Object.

This also explains something else not immediately obvious to the newbie rubyist: things such as puts are not builtins or special cases, but just regular methods in the Kernel class, which is the parent of Object. Whether you call puts at the top level or from inside a class or module, Ruby will look up the class hierarchy and find it in Kernel.

Kernel‘s members include things that might surprise you, like require!

These simple mechanisms—the implicit “main”, the Object class, and the Kernel module—are stunningly elegant design ideas (and, apparently, according to Shaughnessy, ideas adapted from Smalltalk). It allows Ruby to provide the convenient calling syntax of functions, while remaining object-oriented down to its very core.

(Compare this to Python, which does support “real” functions and differentiates them from methods. The distinction is less important in practice, as Python treats them both as “callables”–ie. arbitrary executable things.)

One small note: Shaughnessy writes that “All Ruby functions are actually private methods of Object.” In his example, he prints Object.private_instance_methods and finds his newly defined function there. This is true in Ruby 2.1, but not in 1.9.3, where functions are public:

def hello
 puts "hi there"
end
# In Ruby 1.9.3, this will print 'false' and then 'true'
puts Object.private_instance_methods.member? :hello
puts Object.methods.member? :hello

I am just now beginning to realize how much Ruby is truly an object oriented language through and through. Parts of the language design might seem ad hoc and magical, which is indeed part of Ruby’s beauty and allure, but some very consistent and elegant mechanisms under the hood are what make them work.

(I’ve eagerly ordered Shaughnessy’s book, Ruby Under a Microscope, which looks terrific.)

Leave a Reply

Your email address will not be published. Required fields are marked *