4 ms·
Sometimes you're writing code, and you want to be able to express an idea that your language does not elegantly support. But you know how to express the idea, b
by reikonomusha 4y ago
Sometimes you're writing code, and you want to be able to express an idea that your language does not elegantly support. But you know how to express the idea, but it takes a lot of boilerplate, or is just a massive nuisance to write.
Metaprogramming allows you to write a function that generates the boilerplate, and transparently use that function as if it's a brand new keyword in your programming language. That function in metaprogramming circles is called a "macro", not to be confused with C macros.
Stated another way: a macro = a function that takes as input the code you want to write, and provides as output code that your programming language can actually execute. (It's like a super miniature compiler.)
For example, let's pretend C is metaprogrammable.
Perhaps I want a new keyword "pfor" which is like "for" but executes the loop in parallel.
int x[20];
pfor(i = 0; i < 10; i++) {
x[2*i] = expensive(2*i);
x[2*i+1] = expensive(2*i+1);
}
Here, we assume "expensive" is some really expensive function that maybe might take several seconds to execute, which is why we want to parallelize it.
This "pfor" is just a sort is pseudo code. In reality, you know how we would actually have to write this: make a loop in which we spawn 10 threads, set i to be a thread local variable in each, call a callback function in each thread
void thread_callback(int i){
x[2*i] = expensive(2*i);
x[2*i+1] = expensive(2*i+1);
}
and join the threads at the end. This would have the equivalent effect as our desired "pfor" keyword.
In a metaprogramming language, we can add this keyword by just telling the compiler what code to generate any time it sees "pfor". We could write something like
macro pfor(var = init, cond, incr, body...) {
symbol callback = generate_random_symbol();
```
void $callback(int $var) {
$...body;
}
vector threads;
for($var = $init; $cond; $incr) {
threads.push(make_thread($callback, $var));
}
for(t in threads)
join_thread(t);
```
}
The triple ``` starts a template, and the $ escapes a template. We have to generate a fresh symbol for the callback name so that they don't collide when we use "pfor" multiple times.
Now every time we use "pfor", the compiler will look at our homemade code generator function and write out the code for us.
So in effect by telling the compiler how "pfor" can be implemented in more basic code, we've added "pfor" to the language.
This isn't a separate tool however! It's not a pre-processor or anything like that. It's built in to the language (hence why it's a programming language paradigm).
- hutzlibu 4y agoNow I feel a bit pressure parenting wise, to push my kids, so they will be able to understand all of this, by the age of 5. So I am not sure, I will succeed at it, but I am saving your comment, to try when they are ready. 10 years might be doable. Probably depends how many more times they hit their head.. (And whether I can spark enthusiasm in them, but robots are fun..)
- shepherdjerred 4y agoIs there anything you can do with macros that you can’t do with similar language features like higher order functions?
- mejutoco 4y agoNot OP, but the difference is you can run the code at compile time, so there is no performance penalty.
- reikonomusha 4y agoYes. Macros are evaluated at compile-time. You can put arbitrary compile-time computation in a macro. For instance, you might want to initialize a look-up table at compile time. (Have you ever seen C code where it has a header file with a giant array of pre-computed constants, say, as a lookup table for values of sine? You can just generate that with a macro.) Since macros in effect receive structured source code (basically an AST) as input, you can also do analysis of that code. For example, a macro to simplify algebraic expressions at compile-time. Abstractions based on high-order functions also tend to allocate closures at run-time, so in practice you frequently get a performance penalty for their use. High-order functions remove the need to use macros in some cases, but definitely don't replace all of them.
- somewhereoutth 4y agoNot original questioner, but to rephrase the question slightly: Is there anything you should do with macros that you can’t do with similar language features like higher order functions? My concern with macros is that they are completely open-ended, so you can really do anything - and if you can do anything, then the language is not usefully defining a strict enough semantic structure. As an example, see the 'hygiene problem'.