Macros: more important in low-level languages?

:: Macros, Programming Languages

By: John Clements

Macros are a wonderful thing. A hygienic macro system puts language extensions within the reach of non-compiler-hackers.

However, to date, most modern, hygienic macro systems are associated with languages like Scheme and Racket that are quite high-level. My claim is that macros are probably much more useful in low-level languages. Here’s why:

The cost of abstraction

In high-level languages, programmers are not generally concerned about the cost of abstractions—for instance, function calls. If I have six blocks of similar code, I can probably design a function that accomplishes all six of them. In low-level languages, though, this may not be acceptable from a performance standpoint.

First of all, these languages generally lack tail-calling, so they always always allocate on function calls. Some (much?) of this cost can be ameliorated by inlining, which leads to:

Second of all, even in the presence of inlining, there are often places where the small reorganization required in order to make a function abstraction fit a bunch of similar pieces of code incurs a penalty in the sense that additional allocation may be necessary in order to organize things in a common format.

For both of these reasons, then, macros are more attractive; they allow you to abstract over syntax without incurring these penalties.

The lack of syntactic uniformity

In functional languages,

  • nearly everything is an expression, and
  • functions abstract over expressions.

These two combine to form a nearly universal means of abstraction for similar pieces of code.

In many other languages, though, there are lots of things that are not expressions. Function definitions may not be expressions, annotations and modules and globals won’t be expressions, classes and trait declarations aren’t expressions, etc. Functions can typically only be used to abstract over expressions, and sequences of statements.

This means that in these other languages, abstracting over these other pieces can be impossible. I can definitely imagine wanting, for instance, a tidy way to add a hidden argument to a function, or to add a default trait implementation, or to hide details of a class system boilerplate; all of these are plausible with macros.

Done

So: add a modern, hygienic macro system to your new systems programming language. Hello, Rust!