Aaargh!!

Yet Another Reason Not to Use Macros

Not so long ago, John Hughes presented a module called Aaargh!! to the erlang-questions mailing list. He showed how parse transforms, macros and the Erlang compiler all worked together to mess up with us a bit. I’ll just present that very same story here. If you have read it already, you can safely skip the rest of the article.

AAARGH (from Public Domain)

The Weird Module

This is basically the module that John sent to the mailing list (I changed its name just so it’s easier to avoid the apostrophes)…

-module(weird).
-define(PLEASE_DONT).
-ifdnef(PLEASE_DONT).
-compile({parse_transform, undefined_parse_transform}).
-endif.

The idea is that we first define the PLEASE_DONT macro in a line that we can later comment out if needed (or rather remove and define the macro at compile time).

Then, if the macro is not defined, we run the code through a parse transformation called undefined_parse_transform. The key point here is that this parse transformation module doesn’t exist, so this line should not compile.

But the macro is defined, so the parse transform should not be used, right?

$ erlc weird.erl
src/weird.erl: undefined parse transform 'undefined_parse_transform'

And My Macro?

Turns out that macro is not defined. As Alex points out in the mailing list…

there’s no one-argument define()

As you can see in the docs:

A macro is defined as follows:
-define(Const, Replacement).
-define(Func(Var1,...,VarN), Replacement).

If we change the define line in our code…

-module(weird).
-define(PLEASE_DONT, true).
-ifndef(PLEASE_DONT).
-compile({parse_transform, undefined_parse_transform}).
-endif.

…it compiles perfectly:

$ erlc weird.erl
$ ls weird.beam
weird.beam

Why didn’t you tell me so?

That’s it, right? Well… not so easy. If there is no one-argument define(), then why did the compiler/parser not warned us about it. Check this out:

-module(weird).
-define(PLEASE_DONT).

If we try to compile that module…

$ erlc weird.erl
src/weird.erl:3: badly formed 'define'

What’s going on here?

What we have here are two different kinds of compilation errors:

  • badly formed ‘define’ attributes
  • inexistent parse transformations

We only see one of them because they are found in different stages of the compilation process. To understand this a little bit better you’ll find a video of a talk by Richard Carlsson that deals with the compiler and its friends in a lot of detail below. But in a nutshell: For the parser, one-argument defines (or any other attribute declarations, for what is worth) are perfectly valid even when it does recognize macro definitions, but missing parse transformation modules are not. The compiler is the one that detects one-argument defines as errors. The parser is executed before the compiler and between those two, only the first one that finds an error, gets to shout about it ;)

That’s why when the define is broken, the parser ignores it and proceeds until it finds a missing parse transformation module and then it emits the error and stops the process. But when the parser finds no problems (i.e. there is no missing parse transformation module, in our case), then the compiler is executed, it finds the one-argument define and fails.

Richard Carlsson — Secrets of the Erlang Beam compiler (#EEF17)

Bonus Track

After this whole conversation in erlang-question there was just one extra question in my mind:

If there’s no one-argument define(), what happens if I define a macro in the command line, then?

To test that, I created this module:

-module(weird).
-export([macro/0]).
macro() -> ?THE_MACRO.

I can totally compile the module and specify a value for the macro on command line, like this:

$ erlc -DTHE_MACRO=hello weird.erl
$

If I try to use it in an Erlang shell, I get the following expected result:

1> weird:macro().
hello
2>

Now, what happens if I don’t specify a value for the macro? The docs are a little vague in this area

-D<Name>
 Defines a macro.

Let’s try ourselves, shall we?

$ erlc -DTHE_MACRO weird.erl
$

Since the module did compile, the Erlang compiler should’ve assigned a value to the macro, right? Let’s see what that value is…

1> weird:macro().
true
2>
I don’t know what I expected
Show your support

Clapping shows how much you appreciated Brujo Benavides’s story.