Metaprogramming Ruby
Introduction
I had begun my programming career in languages like Java, C and C++, but had not come across the idea of Metaprogramming. I heard about this technique working on a project that required me to program in Ruby. I remember trying to understand this new language and its funky syntax in terms of what was familiar. Learning through analogy made it was easy to understand the bits that were syntactically similar, but this approach had a converse effect when faced with novelty. This time around my approach is ab initio from fundamentals. This article is a gentle introduction to Metaprogramming in the Ruby context. I will introduce the concept with a simple example, a cheat sheet and finally end with an example on metalinguistic abstraction.
Metaprogramming is essentially programming language features that enable programmers to “create programs that write programs”. In-other terms the ability of a program to modify itself at runtime without the need for recompilation. The Ruby Kernel method eval accepts a String and evaluates its argument within the context of the main program.
File: metaprogramming.rb
a = "World"
eval("puts 'Hello! #{a}' ")
Output# Hello! World
The sub-program could also be:
File: metaprogramming.rb
a = "class Hello; def world; \"Hello! World\"; end; end;"
eval(" #{a}")
puts Hello.new.world # String -> Class Hello containing Method world.
Output# Hello! World
Questions to think about:
- what does
eval
do? - Is it a method hook into the Ruby compiler?
- If indeed a compiler hook will it produce the same output when executed on a Ruby compiler?
- The two examples from above differ in the construction of its sub-program. The second creates a Ruby class, while the first does not. Therefore, is the first program an example of Metaprogramming?
Although, as powerful as eval
is, the idea of a program looking at itself seems more intriguing.
Reflection API
It is a feature of a programming language to examine and modify its structural components during program execution.
BasicObject | Object | Kernel | Module | Class | |
---|---|---|---|---|---|
Create | define_singleton_method | ||||
clone | |||||
dup | |||||
instance_variable_set | |||||
Read | __id__ |
ancestors | |||
class | |||||
superclass | |||||
inspect | |||||
instance_of? | |||||
Update | singleton_method_undefined | ||||
Delete | singleton_method_removed | ||||
eval | __send__ |
||||
instance_eval | |||||
instance_exec |
Metalinguistic Abstraction
One of the key uses of Metaprogramming is its utility in implementing Domain Specific Languages (DSL) or Metalinguistic Abstraction.
Software Engineering like other engineering disciplines deals with complexity by decomposing large systems into simpler and more fundamental units. Therefore, larger software systems are built using language primitives called atoms in a layered approach. Each layer serves as a base for the layer above, finally exposing the user interface. Users interacting with this final layer rarely see the complexity of the underlying system enforced by the idea of abstraction.
It is often desirable to have features in a programming language to extend itself to meet the requirement of a domain called Domain Specific Languages (DSL).
RSpec Example,
describe :testing_group do # Test Group
it :test01 do # Tests
end
it :test02 do
end
end
It can be seen that describe
and it
look like language constructs (keywords), but are methods defined in the RSpec DSL.
Conclusion
This article was designed to be very brief, integrating a cheat sheet with simple examples to demonstrate the basic ideas. However, It will serve as a foundational unit for exploring other ideas.
Please be aware that all material presented in my articles is subject to change. The source of these changes is through feedback and restructuring ideas.