Metaprogramming¶
Metaprogramming is a programming technique in which computer programs can treat other program code as data and modify the program code. This is a powerful technique, but should be used with caution, as it can make the code harder to understand and maintain. In MakrellPy, metaprogramming code is executed at compile time, and can be used to generate code, implement domain-specific languages and more.
Macros¶
A common use of metaprogramming is to define macros, which are code templates that can be expanded into other code. In MakrellPy, macros are defined using the macro
keyword. The macro body is a block of code that is executed when the macro is expanded. The macro body can contain other macros, which are expanded recursively.
Example: Execution Time Measurement¶
{def macro timeit [ns]
{print "This is printed at compile time"}
[
{quote {import time}}
{quote start = {time.time}}
] + ns + [
{quote {print "Time taken:" {time.time} - start}}
]
}
{print "Starting"}
a = 0
{timeit
{while a < 10000000
a = a + 1
}
}
{print a}
{print "Done"}
This macro will add the statements {import time}
and start = {time.time}
before the code block that is passed to it, and the expression {print "Time taken:" {time.time} - start}
after it. The resulting code after macro expansion will look like this:
{print "Starting"}
a = 0
{import time}
start = time.time
{while a < 10000000
a = a + 1
}
{print "Time taken:" time.time - start}
{print a}
{print "Done"}
Running this code will output something like this:
This is printed at compile time
Starting
Time taken: 0.49701976776123047
10000000
Done
Macro Hygiene¶
The previous example demonstrates a common problem with macros called “macro hygiene”. In the expanded code, the variable start
is defined in the macro. This can lead to name conflicts if the variable start
is used elsewhere in the code. To avoid this problem, MakrellPy provides a mechanism called “macro hygiene” that ensures that variables defined in macros do not conflict with variables in the code block. This is done by using the built-in function {gensym}
to generate unique variable names. A new macro can be defined to handle this:
(TBD)
The Meta¶
MakrellPy runs macro definitions and other metaprogramming code at compile time in the “meta” context. meta
blocks can be used to run arbitrary code during compilation. Macros are implemented as functions that take a list of syntax nodes (AST nodes) as input and return a list of syntax nodes as output, running in the meta context. The timeit
macro from the previous example could also be written using a meta
expression:
{meta
{fun timeit [ns]
{print "This is printed at compile time"}
[
{quote {import time}}
{quote {import time}}
{quote start = {time.time}}
] + ns + [
{quote {print "Time taken:" {time.time} - start}}
]
}
}
With meta
, it’s possible to run code that doesn’t fit into macros, such as defining supporting functions or variables, or running arbitrary code.