Skip to content

Metaprogramming

Table of Contents

One of the unique features in Ruby is metaprogramming. This is where a Ruby program can be used to write Ruby methods while the code is executing. While one might think that metaprogramming would only be used in interpreted languages such as Ruby, Python, JavaScript and Lisp, compile time metaprogramming tools can also be found in C++ and Haskell.

Problems with Metaprogramming

Although metaprogramming is powerful and flexible, it doesn’t come without a price, which includes:

Increased complexity

Metaprogramming can make code more complex, as it involves writing code that writes or modifies other code. This added layer can make the logic harder to follow.

Reduced readability

Code that employs metaprogramming techniques can be harder for other developers to read and understand, especially if they are not familiar with these techniques. This is exacerbated by the fact that because metaprogramming is not frequently used, fewer programmers are skilled in metaprogramming.

Harder to debug

Errors in metaprogramming are often more difficult to trace and fix because they may involve multiple layers of code generation or manipulation.

Obscured execution flow

The dynamic nature of metaprogramming can obscure the execution flow, making it difficult to pinpoint where and why an error occurs.

Why use Metaprogramming?

Dynamic Code Creation

Allows for the creation of code at runtime based on dynamic conditions, which can be particularly useful for tasks like creating SQL queries.

Automating Repetitive Tasks

Metaprogramming can automate the generation of boilerplate code, reducing redundancy and the likelihood of errors.

DRY Code

By abstracting common patterns and behaviors into metaprogramming constructs, code duplication is minimized, making the code easier to maintain.

Centralized Control

Changes to a metaprogramming construct can propagate throughout the application, ensuring consistent behavior and reducing maintenance overhead.

Lazy Evaluation

Code can be generated or executed only when needed, potentially reducing resource usage and improving performance.

Creating DSLs

Metaprogramming can be used to create languages tailored to specific problem domains, making the code more expressive and easier to understand for domain experts.

An Example of Metaprogramming in Rails

Rails makes fairly heavy use of metaprogramming to make the framework powerful and flexible. One example would be the find_by_xxx methods, where xxx can be SQL table columns. For example, let’s assume the existence an SQL table with the columns name, email and password. A Rails programmer can call the methods find_by_name, find_by_name_and_email, etc, against the table and the very first time this method is called it won’t be defined. Because of this, the method execution will mutate into a call to the method_missing method, which will then use the method name to construct an appropriate method with the desired SQL query, calling the method as its final step. Future calls to the method will go to the dynamically defined method.

This implementation has the obvious drawback that if the table column changes, then the method will cease to function until it is adjusted to match the table again. As well, the first time the method is called a small performance hit will be incurred while the method is being created.

Ruby Metaprogramming Methods

define_method

A frequently used mechanism to create a method while a Ruby application is running, is define_method.

class_eval

If you want to extend an existing class with a new method, you can either open the class up again, or use class_eval to create the method under the class.

instance_eval

To define a method on a given object instance.

You can also use instance_eval to create a singleton method.

For a more thorough examination of metaprogramming, I suggest that you have a look at the following links: