DEV Community

Cover image for Aaargh!! - Macros, Parse Transforms and others @ Erlang Battleground
Brujo Benavides
Brujo Benavides

Posted on

Aaargh!! - Macros, Parse Transforms and others @ Erlang Battleground

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.

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.
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

…it compiles perfectly:

    $ erlc weird.erl
    $ ls weird.beam
    weird.beam
Enter fullscreen mode Exit fullscreen mode

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).
Enter fullscreen mode Exit fullscreen mode

If we try to compile that module…

    $ erlc weird.erl
    src/weird.erl:3: badly formed 'define'
Enter fullscreen mode Exit fullscreen mode

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.


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.
Enter fullscreen mode Exit fullscreen mode

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

    $ erlc -DTHE_MACRO=hello weird.erl
    $
Enter fullscreen mode Exit fullscreen mode

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

    1> weird:macro().
    hello
    2>
Enter fullscreen mode Exit fullscreen mode

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

-D
Defines a macro.

Let’s try ourselves, shall we?

    $ erlc -DTHE_MACRO weird.erl
    $
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

I don’t know what I expected


This article was originally published in Medium

Top comments (0)