# Metaprogramming

Programs typically read input data, operate on that data and give some output data. Metaprograms read another program, manipulate that program and return a modified program. Sometimes a metaprogram can change its own behaviour by updating itself. The act of writing metaprograms is called metaprogramming. The language used to write metaprograms is called metalanguage.

In general, where code itself needs to change with the data, metaprogramming can be an effective approach. Metaprograms treat code as data, and data as code.

While metaprogramming has been around for decades, it only from the mid-1990s that it received increasing attention. More and more languages are supporting metaprogramming. Principles of metaprogramming have led to higher levels of abstractions such as meta-modeling, metadesign, metaengineering and Model-Driven Engineering (MDE).

## Discussion

• Could you explain metaprogramming with an example?

Assume you're asked to write a program to print numbers 1 to 1000 but without using any looping constructs in the code. The obvious way to do this is to explicitly print each number. Writing such a code becomes tedious for the programmer. For example, in a shell script, you would have to type echo 1, echo 2, and so on till echo 1000. However, it's possible to write another shell script with a for loop that outputs the desired program containing these 1000 echo commands. In other words, we've written a program to produce another program.

In the above example, a shell script produces another shell script. This need not be the case. The metaprogram could be in a different language than the target language. For example, a Ruby script could be used to output C source code.

• What are some use cases of metaprogramming?

Metaprogramming has many applications: compiler generation, application generation, code analysis, generic component design, program transformations, software maintenance/evolution/configuration, anticipatory optimization, design patterns, partial evaluation, and more.

Compilers, transpilers, assemblers and interpreters are examples of metaprograms. They take programs in one form and transform them into machine code, bytecode or even source code in another language. Lex and Yacc are metaprograms. Programs that read database metadata and output EJB or XML code are further examples. Dynamic or interpreted languages usually have an eval() function that allows execution of code supplied as strings. This is also metaprogramming.

ActiveRecord is a database ORM framework used in Ruby on Rails. When calling a method that's not defined, due to predefined conventions, the method gets created on the fly. This implies that a developer writes less manual code.

Metaprogramming has been applied to numerical algorithms such as for solving Ordinary Differential Equations (ODEs).

Another example is the Hone modular graphing library written in Julia. Data points, axes and legends are composed as strings and executed to create the plot.

• What's the formal definition of metaprogramming?

One definition sees metaprogramming as introducing a higher level of abstraction: "the technique of specifying generic software source templates from which classes of software components, or parts thereof, can be automatically instantiated to produce new software components."

We could define metalanguage thus: "any language or symbolic system used to discuss, describe, or analyze another language or symbolic system is a metalanguage." This leads to the definition of metaprogramming: "creating application programs by writing programs that produce programs."

Another definition that emphasizes program generation: "metaprograms manipulate object-programs" where metaprograms "may construct object-programs, combine object-program fragments into larger object-programs, observe the structure and other properties of object-programs." A simpler definition is, "Metaprogramming is writing programs that themselves write code."

• What are the benefits of metaprogramming?

Metaprogramming has many benefits:

• Extensible: Since code is treated as data, it's easy to extend programs. Code can be added by simply adding metadata.
• Performance: Instead of having variables in memory and passing them around, everything is packed into a concatenated string and executed. Metaprogramming enables abstractions without runtime penalty. It can be useful in pushing computations from runtime to compile time. Metaprograms can help tune programs to specific architectures. Specialized efficient programs are better than generic inefficient ones.
• Less Code: In the long term, metaprogramming automates code writing and reduces manual effort. The use of macros (such as in C or C++) saves development time and promotes reusable code. Recurring code patterns can be abstracted and reused even when functions, generics or classes are unable to this.
• Correctness: Compiling code from a high-level language to machine code or bytecode is not something we wish to do manually. If done manually, it's easy to introduce errors. Metaprograms help in such tedious but necessary tasks.
• Reasoning: Metaprograms such as flow analyzers and type checkers help us discover properties about programs, validate behaviour or improve performance.
• How do we classify the various types of metaprogramming?

In Homogeneous MP, the object language and metalanguage are the same. Examples include Racket, Template Haskell, MetaOcaml, and Converge. In Heterogeneous MP, these are different. For example, a Java compiler is written in C. In Generative MP, the metaprogram generates an output program. In Intensional MP, the metaprogram analyzes the input program, by way of reflection. Combining the above two classifications, we note an important class called Homogeneous Generative Meta-Programming (HGMP).

Another classification is based on when metaprograms execute: Compile-Time MP (CTMP) or Run-Time MP (RTMP). CTMP examples include the Lisp family, Template Haskell, Converge and C++. RTMP examples include the MetaML family, JavaScript and printf-based MP. Converge and Scala support both. There are also implicit and explicit flavours of compile-time evaluation.

CTMP and RTMP could be called static and runtime respectively. With RTMP, the metaprogram produces new code, which is immediately executed. If the new code is also a metaprogram, we called this multi-stage programming.

Metaprogramming requires us to annotate static code fragments (the metaprogram) from dynamic ones generated by the metaprogram. This gives rise to two flavours, manually annotated MP versus automatically annotated MP.

• In metaprogramming, how are programs represented as data?

Common ways of representing programs include:

• Strings: Simplest and terse. Typically runtime evaluation but some languages offer compile-time support.
• Abstract Syntax Trees (ASTs): Code fragments represented as a tree. For example, $$2+3$$ can be written as $$ast_{add}(ast_{int}(2), ast_{int}(3))$$.
• Up MetaLevels (UpMLs): Represent AST-like structures as quoted chunks of normal program syntax. $$2+3$$ can be written as $$\uparrow\{2+3\}$$.
• Down MetaLevels (DownMLs): Used to express holes in UpMLs. Evaluated first to yield an AST. If function $$f$$ returns the AST for $$2+3$$, then $$\uparrow\{\downarrow\{f()\} *4\}$$ is a valid expression.

Adopting a suitable representation depends on syntactic overhead, expressivity, support for the target language and support for generating 'valid' programs. Hygiene is also important, which is about managing free and bound variables. Typically, we want the terseness of strings (ASTs are verbose) and the syntactic correctness of ASTs. UpMLs (aka quasi-quotes, backquotes) and DownMLs (aka splices, inserts) offer the best of both worlds.

Lilis and Savidis (2020) give a more complete discussion of metaprogramming models include macro systems, reflection systems, Meta Object Protocols (MOPs), Aspect-Oriented Programming (AOP), generative programming and multi-stage programming.

• Which languages offer support for metaprogramming?

Many languages support metaprogramming including Python, Ruby, JavaScript, Java, Go, Clojure, and Julia. Groovy, Java, Racket, Common Lisp and Scheme are examples that support both CTMP and RTMP. CPP, M4, Racket, Reflective Java and AspectC++ are examples that support Preprocessing-Time MP (PPTMP). Interested readers can refer to A Survey of Metaprogramming Languages by Lilis and Savidis (2020).

Many languages such as Scala, Rust or JavaScript support metaprogramming by design. Others such as C++ evolved to support metaprogramming.

Interpreted languages usually have the eval() function. Examples include Lisp, Perl, Ruby, Python, PHP and JavaScript. In such languages, we could generate the code within the program and pass this code into eval() for execution. For example, we could do this in Ruby: x = 3; s = 'x + 1'; puts eval(s).

UpMLs are supported in Racket, MetaOCaml and Converge. Popular languages including Python, Ruby, Groovy and Perl support MOP based on metaclasses.

• What are some challenges or shortcomings of metaprogramming?

Metaprogramming, both in theory and practice, has many open problems. C++ template metaprogramming is not pretty. Scala had to rewrite it's implementation for metaprogramming. MetaOcaml has typing problems. There's no consistent terminology. Tooling is not mature. For example, it's hard to debug metaprograms.

Executing strings as code can lead to insecure code, particularly when the strings come from untrusted sources. However, languages may provide some support to make it safer. For instance, Python's decorators are safer than strings for metaprogramming.

Writing code as strings reduces readability and is likely to introduce bugs. Strings might use lots of regular expressions. When strings can't be parsed due to some programming error, we might see unexpected output.

Metaprogramming is hard and harder for beginners. Someone who doesn't know the language constructs (such as macros in Lisp) will find it very hard to understand metaprograms written by others.

## Milestones

1940

Willard Van Orman Quine invents quasi-quote and splicing. These concepts would later prove to be useful for metaprogramming. In fact, the term quine is coined decades later to refer to a computer program that takes no input and outputs its own source code. Quines can be considered as special metaprograms.

1963

In a short MIT memo, Timothy P. Hart introduces macros into Lisp. In the 1970s, Lisp becomes possibly the first language to support HGMP.

1997

Two paradigms of metaprogramming emerge: Aspect-Oriented Programming (AOP) and Multi-Stage Programming. It's also during the years 1995-2000 that many metalanguages and metaprogramming systems emerge. Mainstream languages such as Java and C++ provide better support for metaprogramming. The arrival of MetaML proves that HGMP is possible beyond syntactically simple languages like Lisp.

## Sample Code

• # Source: https://en.wikipedia.org/wiki/Metaprogramming
# Accessed 2021-09-09

#!/bin/sh
# metaprogram
echo '#!/bin/sh' > program
for i in $(seq 992) do echo "echo$i" >> program
done
chmod +x program

Author
No. of Edits
No. of Chats
DevCoins
3
0
1073
1622
Words
1
Likes
800
Hits

## Cite As

Devopedia. 2021. "Metaprogramming." Version 3, September 13. Accessed 2022-01-18. https://devopedia.org/metaprogramming
Contributed by
1 author

Last updated on
2021-09-13 10:26:39
• Quine
• Program Introspection
• Reflective Programming
• Aspect-Oriented Programming
• Template Metaprogramming
• Homogeneous Generative Meta-Programming
• Site Map