Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When to use macros functions in Erlang?

Tags:

macros

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.

like image 457
carbon_ghost Avatar asked Jul 20 '16 19:07

carbon_ghost


People also ask

When would you use a macro function?

Macros are used for short operations and it avoids function call overhead. It can be used if any short operation is being done in program repeatedly. Function-like macros are very beneficial when the same block of code needs to be executed multiple times.

What is macro in Erlang?

A macro is defined the following way: -define(Const, Replacement). -define(Func(Var1,...,VarN), Replacement). A macro definition can be placed anyhere among the attributes and function declarations of a module, but the definition must come before any usage of the macro.

What are the advantages of a macro over a function?

When writing macros for functions, they saves a lot of time that is spent by the compiler for invoking / calling the functions. Hence, The advantage of a macro over an actual function, is speed. No time is taken up in passing control to a new function, because control never leaves the home function.


2 Answers

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.

like image 159
I GIVE TERRIBLE ADVICE Avatar answered Oct 06 '22 01:10

I GIVE TERRIBLE ADVICE


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, [], []).
like image 20
Nathaniel Waisbrot Avatar answered Oct 06 '22 00:10

Nathaniel Waisbrot