Although Prolog can be remarkably concise, it's far from ideal when we want to compose function calls. Even an expression as simple as "length of list A appended to list B" must be unwrapped into "append A to B giving C, then return the length of C". I'll explain how I overcame this by writing a preprocessor that translates functional notation into Prolog. It lets me define predicates as if defining functions, and write nested function calls without having to do what Fortran implementors had taught their compilers to do in the 1950s. The version described here works with SWI-Prolog.

- Introduction: the problem with Prolog
- Translating function calls
- Using
`goal_expansion`

- Translating function definitions
- Using
`term_expansion`

- Extending Grips
- What's the semantics?
- Applications

Let us suppose that Prolog's designers had never invented
the arithmetic operator `is`

. Assume instead that they
gave us predicates
such as
`plus`

and `mult`

,
each
performing arithmetic operation on its first
two arguments
and returning the result in the third. Then instead of
writing

X is A*B + A*C.we would have to unwrap the expression so it becomes

mult( A, B, V1 ), mult( A, C, V2 ), plus( V1, V2, X ).

Fortunately, we do have `is`

, which
spares us
this inconvenience when doing arithmetic. But when
we want to manipulate sets, vectors, complex numbers, lists
—
indeed, any time we just want to nest
function
calls
—
we are still forced to do this unwrapping.

So I have written a preprocessor named
"Grips" that
does it for me. One of its benefits
is that I can pass arithmetic expressions as arguments to goals,
avoiding the eternal `NextLength is Length + 1`

's and
`NextCounter is Counter-1`

's that disfigure so many
programs. As an example, instead of defining "factorial" as

fac( 0, 1 ). fac( N, F ) :- Nd is N-1, fac( Nd, Fd ), F is Fd * N.I can code its second clause as

fac( N, F ) :- fac( grips N-1, Fd ), F is Fd * N.

Here, `grips`

is my **evaluation operator**.
It tells the translator to generate a goal that evaluates
`N-1`

, and prepend it to the call of `fac`

.

Indeed, I can move the evaluation operator further out and define:

fac( N, F ) :- F is N * grips( fac( N-1 ) ).Grips knows about

`is`

, so it
will correctly translate this mixture
of arithmetic operators and ordinary predicates.
I can also write `fac`

as a function, promoting functional
evaluation to cover all of the body, as it were.
Here is `fac`

defined in this way:

fac( 0 ) <- 1. fac( N ) <- N * fac( N-1 ).

In the rest of the essay, I'll explain how I implemented Grips for SWI-Prolog. Some of the techniques may be useful if you want to write other preprocessors.

The idea is to translate each expression into a pair consisting of a goal and a "result". The result will be the original expression if that needs no evaluation. Otherwise it will be a variable which the goal will bind to the result of evaluation. For example:

Expression | Goal | Result |

`1` |
none | `1` |

`f(1)` |
`f(1,R)` |
`R` |

`f(g(1),h(2))` |
`g(1,V1),h(2,V2),f(V1,V2,R)` |
`R` |

I do this as follows. If the expression is a number,
return it as the result. The goal doesn't need to do
anything, so
make it `true`

.

Otherwise, assume the
expression
is a function call. Split this into the function, `F`

, and
its
arguments. Translate the arguments into a goal `ArgsGoal`

which
evaluates
them, together with a variable to hold the result of
evaluation. Then conjoin to `ArgsGoal`

another goal
`FGoal`

, formed by calling `F`

on a
list
of the evaluated arguments to which a result variable
is appended.

Here are some examples. To translate
`plus(1,2)`

, no
code is needed to evaluate the arguments, so `ArgsGoal`

becomes
`true`

. The goal `FGoal`

is the function
`F`

—
that is,
`plus`

— called on the evaluated arguments with a result variable
appended.
In this case, the evaluated arguments are the same as the
originals,
so `FGoal`

becomes `plus(1,2,R)`

, where
`R`

is
the result variable.

For the more complicated expression ```
plus( mult(1,2), 3
)
```

,
`ArgsGoal`

becomes
`mult(1,2,R1)`

.
The evaluated arguments becone `[ R1, 3 ]`

,
where
the first one is replaced by the variable holding the result
of
evaluating it. The goal `FGoal`

becomes ```
plus( R1, 3
)
```

. And
finally, the goal for evaluating the entire expression becomes
`ArgsGoal`

conjoined
with `FGoal`

, or

mult( 1, 2, R1 ), plus( R1, 3, R )

Here is the translation code. The predicate
`trans_expr`

translates the expression in its first argument into a
result in
its second and a goal in its third. It calls
`trans_arglist`

to translate function arguments:

trans_expr( Expr, Expr, true ) :- number( Expr ), !. trans_expr( Expr, Expr, true ) :- var( Expr ), !. trans_expr( Expr, ResultVar, Goal ) :- Expr =.. [ F | Args ], trans_arglist( Args, ArgResults, ArgGoals ), append( ArgResults, [ResultVar], ArgsAndResultVar ), FGoal =.. [ F | ArgsAndResultVar ], Goal = ( ArgGoals , FGoal ). trans_arglist( [ Arg1 | Args ], [ Result1 | Results ], Goal ) :- trans_arglist( Args, Results, Goals ), trans_expr( Arg1, Result1, Goal1 ), Goal = ( Goal1 , Goals ). trans_arglist( [], [], true ).

I have added a clause that checks for expressions
that
are variables. These shouldn't occur
as the argument to
`grips`

,
but will turn up when Grips translates definitions, which
could contain variables (possibly from their head) in the
tail
expression, in the way that the second clause for
`factorial`

above did:

fac( N ) <- N * fac( N-1 ).

If you try this code, you will find the generated goals
contain
a good deal of unnecessary `true`

s. By
writing a goal-conjoining predicate, I
made Grips remove these:

conjoin( true, G, G ) :- !. conjoin( G, true, G ) :- !. conjoin( G1, G2, (G1,G2) ).This is a good utility to have whenever we write programs that code-generate Prolog.

`goal_expansion`

SWI-Prolog
contains several **preprocessor hooks**: predicates that
you can
define in order to make the compiler
preprocess various syntactic entities in your
code.
These are not in
the
ISO Prolog standard, but several other implementations, such
as
SICStus,
also provide them. If your Prolog doesn't, you will have to
find
another way to hook your preprocessor into it. The easiest
is to
write your own version of `consult`

.

I use the `goal_expansion`

hook to add the
`grips`

macro mentioned earlier. As it reads a
file,
SWI-Prolog hands each goal `G`

appearing in the body of a
clause to
`goal_expansion`

, passing it as the first
argument. If
the call succeeds, `G`

gets replaced by
`goal_expansion`

's
second argument, assumed to be its translation.

This is how I make `goal_expansion`

act.
First, I write a predicate to translate goals whose
arguments
could contain a `grips`

:

trans_goal( G, GTranslated ) :- G =.. [ F | Args ], trans_goal_args( Args, ArgResults, ArgGoals ), FGoal =.. [ F | ArgResults ], GTranslated = ( ArgGoals , FGoal ). trans_goal_args( [], [], true ) :- !. trans_goal_args( [Arg1|Args], [Result1|Results], Goal ) :- trans_goal_arg( Arg1, Result1, Goal1 ), trans_goal_args( Args, Results, Goals ), Goal = ( Goal1 , Goals ). trans_goal_arg( Arg, Result, Goal ) :- nonvar( Arg ), Arg =.. [ grips , E ], !, trans_expr( E, Result, Goal ). trans_goal_arg( Arg, Arg, true ).This predicate,

`trans_goal`

, runs over the
arguments of
a goal `G`

. If any argument is a term
`grips(E)`

,
`trans_goal`

calls `trans_expr`

on the expression `E`

. It
collects
the
goals for evaluating the arguments and prepends them to
`G`

.
Thus, the
goal
write( grips( plus(1,2) ) )gets transformed into

plus( 1, 2, R ), write( R ).

Notice that the first clause to
`trans_goal_arg`

needs
to test whether it is dealing with a `grips(E)`

.
I took care not to write the `grips(E)`

explicitly, instead
calling `=..`

to test for it. If I hadn't,
then if I already had the preprocessor installed (which I
might
if repeatedly editing, cnsulting, and testing it), it would
try
expanding this particular `grips(E)`

, with
amusing but non-terminating results.

Now I can connect `trans_goal`

to `goal_expansion`

, by writing
a clause for the latter.
I make this clause test whether the goal actually
needs translating
— whether it does contain a `grips`

— and fail
if
not.
This is good practice, because some Prologs might call
`goal_expansion`

over and over again on the same goal if I make it return
the original
without any change. Here is the code:

needs_translating( G ) :- nonvar( G ), G =.. [ _ | Args ], member( Arg, Args ), nonvar( Arg ), functor( Arg, grips, 1 ), !. :- multifile user:goal_expansion/2. :- dynamic user:goal_expansion/2. user:goal_expansion( G, GTranslated ) :- needs_translating( G ), !, trans_goal( G, GTranslated ).As with

`trans_goal`

, I've taken care to avoid an
explicit
`grips(E)`

in the code.
There could be could be several different
clauses for
`goal_expansion`

in force at the same time if you
or
someone else have installed other preprocessors. You'll
need
to ensure these work correctly together, especially if a
single
goal contains constructs from different preprocessors, or
gets translated
by one preprocessor into a construct handled by another.

Translating function definitions is now straightforward. To translate

double(N) <- plus(N,N).I translate

`plus(N,N)`

, giving the goal
`plus(N,N,R)`

.
I use this as the tail of the new predicate. I then insert
`R`

as the final argument of `double(N)`

. And
lo and behold:
double( N, R ) :- plus( N, N, R ).

Here is the translation code, in which the main predicate
is `trans_def`

:

:- op( 1200, xfx, user:(<-) ). trans_def( (Head <- Expr) , (PrologHead:-Tail) ) :- !, trans_expr( Expr, ResultVar, Tail ), trans_head( Head, ResultVar, PrologHead ). trans_head( Head, ResultVar, PrologHead ) :- Head =.. [ F | Args ], append( Args, [ResultVar], ArgsAndResultVar ), PrologHead =.. [ F | ArgsAndResultVar ].

`term_expansion`

In the same kind of way that I connected
`trans_goal`

to `goal_expansion`

,
I can now connect `trans_def`

to the `term_expansion`

preprocessor hook.
This resembles
`goal_expansion`

, but
translates complete terms in the source file.

For Grips, `term_expansion`

is simpler
to use than
`goal_expansion`

:
I can test whether a definition needs translating
just
by whether it contains a `<-`

connective; I
don't
need to decompose definitions in the same way I did with
goals;
and I don't need to worry about avoiding explicit calls to
`<-`

in the translator. Here is the code:

:- multifile user:term_expansion/2. :- dynamic user:term_expansion/2. user:term_expansion( Def, DefTranslated ) :- trans_def( Def, DefTranslated ).

For real-world applications, the code
above will need
extending. For example, I have made Grips treat the empty-list atom
`[]`

as a constant that stands for
itself
in the same way as numbers. Non-empty lists, it
evaluates element by element.

One construct that I added is an
expression-disjunction
operator named `;`

. The expression
`E1;E2`

evaluates `E1`

and returns its result if it
succeeds, otherwise returning that of `E2`

.

Turning to arithmetic, Grips
translates `+`

, `*`

and other
arithmetic
operators into calls to `is`

. I
find this terribly convenient.

We do need to take care with the semantics. One question is
how to
distinguish between atom constants and functions of no
arguments.
For example, how should we invoke `read/1`

in
functional notation?
We can't write `read()`

, because it's not valid
syntax.
But if we make Grips take the atom `read`

to denote
a call
to the predicate, how can we write the atom constant
`read`

?

On the other hand, if `read`

denotes the atom,
then we
need a special notation for the function call. I
eventually
decided that an atom on its own should be a call, and that
to make
it a literal, I should precede it by a
backquote operator ```

.
In fact, I use ```

to quote
any
term, not just atoms.

A different question: what about the evaluation operator
`grips`

?
Should the
translator recognise it if it occurs at any level inside a
goal,
or only at the top level? That is, should the goal

write( pair( grips( plus(1,2) ), 3 ) )stay as it is, or evaluate to

`write( pair( 3, 3 ) )`

?
I use Grips more than I do Definite Clause Grammars. It is just so convenient when writing complicated expressions. It also saves thinking up names for result variables, thus mitigating the psychological disorder known as "naming fatigue" — a steadily increasing inability, as the day wears on, to invent meaningful identifiers.

Functional notation is also wonderful because it makes data flow explicit. When reading a goal such as

bar( C, D, A ), foo( A, B ), fred( B, D )one has to examine the predicate definitions and accompanying comments or mode declarations before being sure of the data flow. But the same call in functional notation makes the flow immediately clear:

fred( foo( bar(C,D) ), D )So I have used functional notation in teaching Prolog, to make it easier for novices to read Prolog code.