Using the macros

## 12.2 Using the macros

The following declarations and built-ins control macro expansion:

local macro(+TermClass, +TransPred, +Options)
  define a macro for the given TermClass. The transformation will be performed by the predicate TransPred.
export macro(+TermClass, +TransPred, +Options)
  as above, but available to other modules.
erase_macro(+TermClass, +Options)
  erase a currently defined macro for TermClass. This can only be done in the module where the definition was made.
current_macro(?TermClass, ?TransPred, ?Options, ?Module)
  retrieve information about currently defined visible macros.

Macros are selectively applied only to terms of the specified class. TermClass can take two forms:

Name/Arity
  transform all terms with the specified functor
type(Type)
  transform all terms of the specified type, where Type is one of `compound, string, integer, rational, float, breal, atom, goal`1.

The +TransPred argument specifies the predicate that will perform the transformation. It has to be of arity 2 or 3 and should have the form:

>     trans_function(OldTerm, NewTerm [, Module]) :- ... .

At transformation time, the system will call TransPred in the module where macro/3 was invoked. The term to transform is passed as the first argument, the second is a free variable which the transformation predicate should bind to the transformed term, and the optional third argument is the module where the term is read or written.
Options is a list which may be empty (in this case the macro defaults to a local read term macro) or contain specifications from the following categories:

- mode
  read:
    This is a read macro and shall be applied after reading a term (default).
  write:
    This is a write macro and shall be applied before printing a term.
- type
  term:
    Transform all terms (default).
  clause:
    Transform only if the term is a program clause, i.e. inside compile/1, assert/1 etc. Write macros are applied using the 'C' option in the printf/2 predicate.
  goal:
    Goal-read-macros are transformed only if the term is a subgoal in the body of a program clause. Goal-write macros are applied using the 'G' option in the printf/2 predicate.
- additional specification
  protect_arg:
    Disable transformation of subterms (optional).
  top_only:
    Consider only the whole term, not subterms (optional).

The following shorthands exist:

local/export portray(+TermClass, +TransPred, +Options)
  portray/3 is like macro/3, but the write-option is implied.
inline(+PredSpec, +TransPred)
  inline/2 is the same as a goal-read-macro. The visibility is inherited from the transformed predicate.

Here is an example of a conditional read macro:

>     [eclipse 1]: [user].
>      trans_a(a(X,Y), b(Y)) :-    % transform a/2 into b/1,
>             number(X),           % but only under these
>             X > 0.               % conditions

>     :- local macro(a/2, trans_a/2, []).
>       user       compiled traceable 204 bytes in 0.00 seconds

>     yes.
>     [eclipse 2]: read(X).
>             a(1, hello).

>     X = b(hello)                 % transformed
>     yes.
>     [eclipse 3]: read(X).
>             a(-1, bye).

>     X = a(-1, bye)               % not transformed
>     yes.

If the transformation function fails, the term is not transformed. Thus, a(1, zzz) is transformed into b(zzz) but a(-1, zzz) is not transformed. The arguments are transformed bottom-up. It is possible to protect the subterms of a transformed term by specifying the flag `protect_arg`.
A term can be protected against transformation by quoting it with the “protecting functor” (by default it is no_macro_expansion/1):

>     [eclipse 4]: read(X).
>             a(1, no_macro_expansion(a(1, zzz))).
>     X = b(a(1, zzz)).

Note that the protecting functor is itself defined as a macro:

>     trprotect(no_macro_expansion(X), X).
>     :- export macro(no_macro_expansion/1, trprotect/2, [protect_arg]).

A local macro is only visible in the module where it has been defined. When it is defined as exported, then it is copied to all other modules that contain a use_module/1 or import/1 for this module. The transformation function should also be exported in this case. There are a few global macros predefined by the system, e.g. for `-``->/2` (grammar rules, see below) or `with/2` and `of/2` (structure syntax, see section 5.1). These predefined macros can be hidden by local macro definitions.
The global flag macro_expansion can be used to disable macro expansion globally, e.g. for debugging purposes. Use `set_flag(macro_expansion, off)` to do so.
The next example shows the use of a type macro. Suppose we want to represent integers as s/1 terms:

>     [eclipse 1]: [user].
>      tr_int(0, 0).
>      tr_int(N, s(S)) :- N > 0, N1 is N-1, tr_int(N1, S).
>      :- local macro(type(integer), tr_int/2, []).

>     yes.
>     [eclipse 2]: read(X).
>             3.

>     X = s(s(s(0)))
>     yes.

When we want to convert the s/1 terms back to normal integers so that they are printed in the familiar form, we can use a write macro. Note that we first erase the read macro for integers, otherwise we would get unexpected effects since all integers occurring in the definition of tr_s/2 would turn into s/1 structures:

>     [eclipse 3]: erase_macro(type(integer)).

>     yes.
>     [eclipse 4]: [user].
>      tr_s(0, 0).
>      tr_s(s(S), N) :- tr_s(S, N1), N is N1+1.
>      :- local macro(s/1, tr_s/2, [write]).

>     yes.
>     [eclipse 2]: write(s(s(s(0)))).
>     3
>     yes.