New library(macros)

I pushed a new library(macros). This is intended to deal with the common need to declare compile time constants, write more complex terms using a simpler syntax, etc.

The overall idea is that a macro is used as #name(Arg, ...), i.e., is of the same #(callable), where # is a prefix operator, so we can write #callable.

Macros are defined using one of

#define(Macro, Expansion).
#define(Macro, Expansion) :- Body.

In addition, macro may be imported from another module using

#import(File).

Macros are scoped to a module.

Macro matching uses single sided unification (SSU) trough => rules. A very simple example is here:

:- use_module(library(macros)).
#define(max_size, 100).

Now we can use e.g.

(  Size < #max_size
-> ...

Using the Body, we can also do more fancy stuff. For example, we can define

#define(calc(Expr), Value) :- Value is Expr.

Now, we can anywhere use e.g. #calc(pi/2) for using this floating point constant. Of course, expression optimization can avoid evaluating pi/2 at runtime in A is X*pi/2, but our macro allows us to use the compile time evaluated value anywhere, not just in the context of arithmetic.

This was designed to deal with simplifying writing complex terms in a project.

Comments are welcome!

8 Likes

Very cool!

One issue that I found is that library(macros) seems to conflict with library(clpfd) with set_prolog_flag(clpfd_monotonic, true), where CLP(FD) also relies on #/1 terms.

This is a bit of an edge case, but for example, if we have in macros_test.pl:

:- module(macros_test, [baz/1]).

:- use_module(library(clpfd)).

:- set_prolog_flag(clpfd_monotonic, true).

:- use_module(library(macros)).

#define(foo, bar).

baz(X) :- #(X) #= 1 + 2.

Then trying to load this file yields an instantiation error:

$ swipl macros_test.pl
ERROR: .../macros_test.pl:11:
ERROR:    Arguments are not sufficiently instantiated
ERROR: Exported procedure macros_test:baz/1 is not defined
Welcome to SWI-Prolog (threaded, 64 bits, version 9.1.10-9-g1dd0edbaa)

I have been dreaming about constants for a long while already … makes live so much easier.

I don’t think it’s an edge case at all. Working with integer arithmetic is a very common place to have this kind of constants defined in the source code, and not being compatible one library with the other will be a bummer.

Good points. Pushed a commit that only considers #Term for expansion if Term is a Prolog callable term (atom or compound). Instead of raising an error, #Var is now passed unmodified.