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,
goal1.
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.