Examples of Metaprogramming – A Guide for Beginners

1
196

Today I’m going to walk through examples of metaprogramming. Wait, don’t run away! Metaprogramming is one of the more misunderstood topics in development, however it’s actually quite easy to understand if you look past the confusing name.

What is Metaprogramming?

If you’re like me the first time I heard the term metaprogramming my eyes glazed over. But let’s take a look at what metaprogramming actually is and later on we’ll review some practical examples of metaprogramming.

Technically metaprogramming is code that writes code. However, before you get visions in your mind of robots writing code to take over the world, understand that it’s actually much less intense.

Metaprogramming allows programs to create methods on the fly instead of having to define them in the program itself.

Examples of Metaprogramming

A Basic Example

Let’s begin with a basic example. I’m going to start with an example from the Ruby on Rails framework. Imagine that you have a database table for users that have columns such as name and email.

Obviously the Ruby on Rails framework doesn’t know what columns that you’re going to have for the user database. Which may cause you to be surprised that this database query will actually work:

examples of metaprogramming

So how exactly does the method find_by_email work when the method wasn’t actually defined by you or the framework? Is it some kind of dark magic?

examples of metaprogramming

Not exactly. The Rails framework dynamically creates methods based on the column names in your database. So essentially the method is never defined, instead it is generated on the fly by leveraging metaprogramming techniques.

Practical Examples of Metaprogramming

When I’m learning a new topic I like to start by walking through base case examples. With that in mind we’re going to follow that pattern in this guide.

All of the examples that we’re going to go through will be in the Ruby language. I chose Ruby because it has a flexible metaprogramming interface and it’s very expressive.

Metaprogramming Options

There are a number of ways to implement metaprogramming. And the method you choose should be dictated by what type of feature you need to build. A few of the key options are:

  1. Leveraging monkey patching.
  2. Defining methods at application runtime.
  3. Defining methods on the fly.

If number 2 and 3 sound similar it’s because they are. In this guide we’ll examine the subtle differences with examples.

Examples of Metaprogramming: Monkey Patching

Starting off the list for examples of metaprogramming is monkey patching. Yes, it’s a weird name, but it’s a powerful and dangerous tool.

Example of Monkey Patching

Monkey patching is an object oriented programming technique that allows developers to:

  • Open up classes
  • Add or alter built in behavior

examples of metaprogramming

Let’s take a look at this code example. In this file I’ve opened up the built in Ruby String class. Inside of the class I’ve added a new method called gibberish that simply returns a string of random numbers.

examples of metaprogramming

If I call the method on a string it no longer prints out the string that was entered and instead prints out the random set of string characters. This is monkey patching in action. It essentially lets you open up classes and alter behavior.

When Monkey Patching is a Bad Idea

I mentioned earlier that this can be a dangerous practice, let’s see why.

Ruby has a built in method in the String class called upcase that converts all of the characters to uppercase values.

examples of metaprogramming

What would happen if we messed with the upcase method? If you guessed that it could cause some nasty and confusing errors, you’d be 100% correct.

examples of metaprogramming

Even if your monkey patched method didn’t cause an error, it could cause some negative side effects throughout a program if any other developers are relying on the method that you altered.

For that reason it’s important to proceed with caution when it comes to performing monkey patching. Typically I won’t override Ruby core methods.

When Monkey Patching is a Good Idea

So when is monkey patching a good idea? I’ve had a number of times where outside libraries were causing issues in a program that I was working on. By leveraging monkey patching I was able to fix the bugs, which didn’t cause any negative side effects in the program.

Examples of Metaprogramming: Defining Methods at Application Runtime

Next on the list for examples of metaprogramming is defining methods at application runtime. What exactly does that mean? Well, have you ever had a set of methods that were very similar?

examples of metaprogramming

Let’s imagine that we have a Book class that prints out details about each genre of book that our application manages. Do you notice how similar the methods look? This is a really poor programming practice because it requires us to change each method whenever we have to update the output for a book’s genre details.

Wouldn’t it be nice if we could simply define the method one time and then have the methods for genres generated each time the program runs? Well thankfully we can do that in Ruby.

examples of metaprogramming

We can get rid of all of the manual method calls and simply list the genres in an array. From there we can loop through the list and call define_method. This will generate the full list of methods, just like we had before. Except now whenever we have to change the details functionality we will only have to make the changes in a single place.

This is a great tool for removing duplicate code in classes and I find it helpful in many applications that I work with.

Examples of Metaprogramming: Defining Methods on the Fly

Last on the list of metaprograming implementations is to define methods on the fly. This option is one of the most powerful, but also can be confusing, so let’s walk through it as practically as possible.

Continuing with our book example, let’s imagine that you need to create a feature for searching through your list of authors. But instead of manually querying author details you want to have custom methods available to use. This type of design pattern is used frequently when building code libraries such as Ruby gems.

Method Missing Example

So for example, we want methods such as author_genre to print out the author’s genre. However the tricky part is that our library may not know what the database column names will be. It could be genre, or it could be summary, etc.

With this in mind we can’t use define_method because we don’t know the exact names to put them in a list. This is where method_missing comes in. Whenever Ruby comes across a method that isn’t defined in a program it looks to see if the developer implemented the method_missing construct.

Essentially you can use method_missing to let the program know of any types of methods you want to be generated on the fly.

examples of metaprogramming

In this program I’m using the Open Struct library to mimic creating an Authors database. In a real world program we’d use a database, but the functionality, from a metaprogramming implementation perspective, is the same.

How does method_missing work?

The code explanation is a little out of the scope of this guide, if you want a deep dive into a method_missing tutorial I have a full section dedicated to it in my Comprehensive Ruby Programming course.

At a high level we’re defining the method_missing method. Which is something that Ruby looks for whenever it encounters a method that isn’t explicitly defined in the class.

From there we’re passing in the name of the attribute that we’re looking for, such as genre or first_name. From that point we tell method_missing to pass that information to our mocked database to retrieve the attribute we called.

This means that author_genre will actually function like a method, even though it was never explicitly defined by us in the method, pretty cool right?

It’s also important to implement respond_to_missing? since it is a tool that many developers use to ensure that a method has been made available via metaprogramming.

How is this Different than Using define_method?

It may seem like method_missing and define_method are pretty similar, and they are. The key to remember is that define_method generates the methods when the application starts and is typically based on a list of values.

Whereas method_missing is more dynamic and is checked whenever the method is called. This gives it more flexibility and is considered one of the most critical aspects to implementing metaprogramming in Ruby.

Drawbacks to Metaprogramming

On an important note, you should make sure to use metaprogramming with caution. Through the years I’ve witnessed many metaprogramming implementations and I’ve seen where they have unnecessarily over complicated a code base.

As you may have noticed, processes such as method_missing are more challenging to read compared with normal Ruby code. This means that programs filled with metaprogramming modules can make it difficult to work with for new developers.

So make sure you consider the long term implications of your metaprogramming implementations. Just because you’ve created a metaprogramming component that works doesn’t mean that you should include it in your codebase. Like any other piece of your program, metaprogramming features should be easy to understand and should be modular so they can be changed in the future.

Remember the #1 rule of development: program requirements always change.

Summary of Metaprogramming

I hope that this has been a helpful list of examples of metaprogramming and that you have a good idea of how you can effectively utilize metaprogramming in your next project.

1 COMMENT

  1. I tried your method_missing example but I am still getting
    method_missing.rb:24:in `’: undefined method `author_genre’ for # (NoMethodError)
    error

LEAVE A REPLY

Please enter your comment!
Please enter your name here