carbon_ghost
carbon_ghost

Reputation: 1124

When to use macros functions in Erlang?

I'm currently following the book Learn You Some Erlang for Great Good by Fred Herbert and one of the sections is regarding Macros.

I understand using macros for variables (constant values, mainly), however, I don't understand the use case for macros as functions. For example, Herbert writes:

Defining a "function" macro is similar. Here's a simple macro used to subtract one number from another:

-define(sub(X, Y), X-Y).

Why not just define this as a function elsewhere? Why use a macro? Is there some sort of performance advantage from the compiler or is this merely just a "this function is so simple, let's just define it in one line" type of thing?

I'm not trying to start a debate or preference argument, but after seeing some production Erlang code, I've started noticing lots of macros function usage.

Upvotes: 5

Views: 2156

Answers (2)

I GIVE TERRIBLE ADVICE
I GIVE TERRIBLE ADVICE

Reputation: 9648

In this case, the one obvious advantage of the macro not being a function (-define(sub(X, Y), X-Y), which would be safer as -define(sub(X, Y), (X-Y))) is that it can be used as a guard since custom function calls are forbidden.

In many cases it would otherwise be safer to define the function as an inlined one.

On the other hand, there are other interesting cases, such as assertions in tests or shortcuts where what you want is to keep some local context in the final place.

For example, let's say I want to make a generic call for a test where the objective is 'match a given pattern and return a given value, or fail after M milliseconds'.

I cannot make this generic with code since patterns are not data structures you are allowed to carry around. However, with macros:

-define(wait_for(PAT, Timeout),
        receive
            PAT -> VAL
        after Timeout ->
            error(timeout)
        end).

This macro can then be used as:

my_test() ->
    Pid = start_whatever(),
    %% ...
    ?wait_for({'EXIT', Pid, Reason}, 5000),
    ?assertMatch(shutdown, Reason).

By doing this, I'm able to simplify the form of text in some tests without needing a bunch of nesting, and in a way that is not possible with functions.

Do note that the assertion itself as defined by eunit is using a function macro, and does something akin to

-define(assertMatch(PAT, TERM),
        %% funs to avoid leaking bindings into parent scope
        (fun() ->
           try
              PAT = TERM,
              true
           catch _:_ ->
              error({assertion_failed, ?LINE, ...})
           end
         end)()).

This similarly lets you carry patterns and bindings and do fancy forms that couldn't be possible otherwise.

In this last case, you'll notice I used the ?LINE macro. That's another advantage of macros: you preserve information and locality about the call site, such as its module name, line number, and so on. This is useful when such metadata is required, such as when you're reporting test failures.

Upvotes: 4

Nathaniel Waisbrot
Nathaniel Waisbrot

Reputation: 24473

If you're looking at old code, there might be macros used as a way of inlining small functions under the assumption that function calls are very expensive. I'm not sure if that was ever true, but it's not something you need to worry about today.

Macros can be used to define constants, like

-define(MAX_TIMEOUT, 30 * 1000).

%% ...
  gen_server:call(my_server, {do_stuff, Data}, ?MAX_TIMEOUT),
%% ...

I mostly prefer to pass in environment variables for this job, but it's more work to read them on startup and stash them somewhere and write accessors.

Finally, you can do some simple metaprogramming:

-define(MAKE_REQUEST_FUN(Method),
        Method(Request, HTTPOptions, Options) ->
          httpc:request(Method, Request, HTTPOptions, Options)).
?MAKE_REQUEST_FUN(get).
?MAKE_REQUEST_FUN(put).

%% Now we've defined a get/3 that can be called as
%% get(Request, [], []).

Upvotes: 2

Related Questions