[m-dev.] pragmas should specify pred/func as well as name and arity

Zoltan Somogyi zoltan.somogyi at runbox.com
Thu Apr 29 12:38:21 AEST 2021


Most pragmas tell the compiler something about a predicate
or function. We have two pieces of syntax that pragmas use to specify
*which* predicate or function. The first syntax we implemented was simple name/arity.
The other, implemented later, is either pred(argmode1, ..., argmodeN),
or func(argmode1, ..., argmodeN) = retmode.
The latter of course was intended to be more precise, because it was
intended to specify one *procedure* of a predicate or function. But it is
also more precise in another way: it actually specifies whether it applies
to a predicate or function, which the first syntax does not.

This leaves open the door to a potential problem where the programmer
intends a pragma to apply to e.g. pred foo/2, but the compiler also
applies it to func foo/2.

This has not been a significant problem in the past, for two reasons.
First, the first pragmas we implemented (apart from things like foreign_procs,
which look like pragmas syntactically but which we now handle as the
totally separate kind of thing they are) were things like pragma inline,
for which it did not matter all that much whether it applied to both
a pred and a func, or to just the one the programmer meant.
The second reason, unique to us, is that we tended to frown on
having both a pred foo/2 *and* a foo/1, so the issue was rarely relevant,
though we *do* have some instances of defining e.g. both pred foo/2
and pred foo/3, and then *function versions of both*, i.e. func foo/1
and func foo/2.
 
However, for pragmas that assert something, such as promise_pure,
applying them to both a predicate and a function when the programmer
intended applying them to only one is a more significant problem.
I believe we should update the pragmas that use the first syntax
to first allow, and eventually require, the programmer to say whether
they mean pred foo/2 or func foo/2.

I can see three broad approaches to how this could be done.

Approach 1 would be to wrap the name/arity pair in either pred() or func().
This would look good, but unfortunately the second syntax also uses terms
whose top function symbol is either pred or func, and there are some pragmas
that allow the subject that they apply to to be specified using either the first
or the second syntax (see attached table), so this would probably serve
as a source of confusion. And the compiler's error messages, having to
describe two possible ways to fix any syntax errors (one to reach each kind
of valid syntax), couldn't help as much as we would like them to.

Approach 2 would be to add a "_pred" or "_func" suffix to the pragma name,
such as ":- pragma inline_func(foo/2).". We have already used this approach
when replacing ":- external(foo/2)." with ":- external_{pred,func}(foo/2).".
However, there are some pragmas, such as "terminates", for which neither
that suffix, nor a similar prefix, would look all that natural.

Approach 3 would be to add an extra  argument, containing simply either
"pred" or "func", to the pragma's argument list, just before the argument
that now contains just foo/2.

There are also blended approaches: we could require a suffix such as "_proc"
for pragmas that specify the argument modes, a form of approach 2,
and then require a pred() or func() wrapper for the form of the
pragma that does not specify modes, i.e. approach 1. The point of
separating out the _proc form is to mitigate the error message problem
described above. As the attached table shows, only a few kinds of pragmas
allow the subject of the pragma to be *either* a single procedure
or all procedures in the named function or predicate.
For example, we could replace the type_spec pragma with three
others: type_spec_pred and type_spec_func when you want
type specialization for all modes, and type_spec_proc when you want it
for just one.

There is also what we might call "approach 0": simply obsolete the pragmas,
such as promise_pure, whose effect can be accomplished by wrappers
around goals.

Does anyone have any other approaches to fixing this problem?
And what are your preferences?

It would be simplest if we chose the same approach for all pragmas,
but we could adopt different approaches for different pragmas.

We would need to introduce any such changes gradually. We would
implement any new syntax alongside the old, generating messages
telling programmers to switch to the new form when we find the old.
These messages could have severity warning, but with an option to
shut them up, such as --no-warn-for-old-pragma-syntax. That would
need to persist past the next stable release, after which we could
first up the severity to error, and then later still disable the code
parsing the old syntax altogether.

Opinions?

BTW, in case anyone wanders what brought up this problem:
the code in recompilation.check.m that I have worked on
the last day or two needs to know what entities a pragma
applies to if it is to make correct decisions about when
a recompilation is needed.

Zoltan.


More information about the developers mailing list