Introduction of Meta Programming in Ruby

Carli Chan
Level Up Coding
Published in
5 min readMar 12, 2022

--

Photo by Daniele Franchi on Unsplash

Despite the growing interest in Metaverse, metaprogramming is still unknown to many new developers today. In this article, we will explore what is metaprogramming, why it is beneficial, and how it is incorporated into the Ruby language to underline the language’s uniqueness.

Introduction

The prefix “meta” may be overused today to imply something mysterious and beyond the comprehension of a normal human being, yet the real connotation of metaprogramming could be much more attainable and comprehensible. Metaprogramming simply refers to the technique of writing code that can manipulate or generate code.

In traditional programming, we write code that can receive, manipulate, and output data. The program itself is static, meaning all the code remains unchanged during the phase of execution, while the data becomes dynamic. This type of programming is what most people are familiar with. We write a function that can process some information and output the expected solution to us.

In metaprogramming, however, both the program and the program execution becomes dynamic, which allows us to write a function that can process some code, and can output an altered and processed version of the code. In essence, we treat code just like data. A good analogy of the duality or interchangeability between code and data would be wave-particle duality that particles, like electrons, exhibit.

The concept of metaprogramming is rather simple, but what we can achieve with it is multifold. A few objectives of metaprogramming is listed below:

  • Code generation
  • Code completion
  • Code reflection

Metaprogramming in Ruby

After learning all the relatively vague and theoretical definitions of what metaprogramming is, let’s really familiarize ourselves with this rather powerful concept. As a full-stack developer myself, I see metaprogramming being incorporated rather ubiquitously in the Ruby language, thus I would solidify the abstract concepts and theories through examples conveyed in Ruby in the following sections.

Consider the following example:

class Person  def initialize(name)    @name = name  end  def self.breathe  end  def dance    puts "I am dancing!"  endend

We can easily identify that “dance” is an instance method and that “breathe” is a class method considering the addition of the “self” prefix.

Now, let’s instantiate the “Person” class:

Ana = Person.new("Ana")

The instance “Ana” can call the instance method “dance” to inform the users that she is dancing:

Ana.dance
# => "I am dancing!"

The Ruby language does a wonderful job at hiding code from users. What we did not see here is that Ruby created a metaclass for the object “Ana” and the “Ana.dance” method is held within the metaclass.

Metaclass, also called eigenclass or singleton class, is generated during the execution of the program. This is where metaprogramming comes into play. The code we wrote generated new code that helped us to create the metaclass.

Recall that almost everything in Ruby is an object, meaning that Ruby also generated a metaclass for the “Person” class. In fact, the class method “breathe” is contained inside of it.

It might be difficult to understand this concept at first. Simply imagine the metaclass as a “personal assistant” of an object. This “personal assistant” helps an object to carry all the methods. The “Ana” object has a method “dance” that is carried by its metaclass, and similarly, the “Person” class, which is also an object, has a method “breathe” that is carried by the metaclass of “Person”. This analogy is an oversimplified picture and certainly not an accurate outline of the relationship between objects and metaclasses, but simply consider this as a vague interpretation of metaclass.

After peeking into how Ruby utilizes metaprogramming internally, we, as developers, may want to exploit the versatility of metaprogramming. Indeed, we have multiple methods in Ruby that can achieve just that.

define_method method

One of the powerful metaprogramming methods that can allow us to define methods during execution is the define_method method.

Let’s consider the following example:

class Person  def watch_tv    puts "I am watching tv"  end  def watch_netflix    puts "I am watching netflix"  endend

The code above seems to be repetitive but there’s nothing we can do to DRY it up. Actually, we can DRY the code up by using define_method. Consider the following code to see how it could be done:

class Person  ["tv", "netflix"].each do |method|
define_method "watching_#{method}" do
puts "I am watching #{method}"
end
end
end

Let’s break down the example above. We first loopped through an array containing strings, then we defined method names and bodies according to the strings in the array using the powerful define_method method. What is powerful about this is that the two methods, “watching_tv” and “watching_netflix”, are generated by the code we wrote, resulting in a dynamic program execution. This is a rudimentary example of exploiting the power of metaprogramming. Further reading is strongly recommended.

Conclusion

Now, let’s revisit the prefix “meta”. The connotation of “meta” is derived from Greek in which it simply means “beyond”. Therefore, metaprogram is merely a program that is one step beyond regular programs. With the ability to write and manipulate regular programs as if they are the input data, metaprogramming could effectively add an extra layer of abstraction to our existing programming framework, as well as broaden the expressivity of a programming language.

Obviously, I have intentionally left out a lot of nuances of metaprogramming to keep the reading as smooth and concise as possible. We may ask: how do metaclasses behave in the inheritance tree? The answers to your questions may lay within the list of further reading material I appended at the bottom. This article hopefully serves as a kickstarter for your journey to dive deeper into the research related to metaprogramming and is by no means a comprehensive guide on the subject matter.

--

--