Term_expansion, goal_expansion, and peers

As explained here:
term_expansion/2 is the original [all-purpose, most-general, program-transformation] predicate,
goal_expansion/2 was added later to handle a common case and prevent the user from “having to deal with all control stuff”.

In a similar vein, I’d like to transform “goals” of a DCG rule before dcg_translate_rule/2 modifies it.
I have this working (at least for the moment).

Browsing SWI source related to goal_expansion, I find things like this, where SWI has to account for goal_expansion having happened. But if goal_expansion is implementable via term_expansion, isn’t term_expansion the thing that needs to be accounted for? If we use term_expansion, what “accounting” must we perform?

First of all, the expansion is a bit of a mess. Once upon a time with small programs and no modules it was fine, but gradually more and more features were added. As is, SWI-Prolog uses a pipe line for each translation that starts in the target module, than applies the rules of the user module and finally the system module. First we do term expansion, then we expand DCG rules and finally we do goal expansion.

This is all probably not a good idea and might change at some point. Notably making the transformations modular and controlling the order is hard. Several systems has “solved” this, but all in different ways. Ideally we’d replace it by something that is more widely supported than just SWI-Prolog. That, as well as keeping an new incarnation pretty compatible is a big challenge.

As you probably found, you can define a term expansion rule for --> and do your own expansion where you indeed have to redo most of the logic to unwrap control structures that is already part of expand_goal/2. I’m not sure how much demand there is to do this differently. After all, you can also replace the output of the DCG expansion. What is the problem doing that?

The stuff in library(prolog_clause) is trying to do the reverse: relate the compiled code back to the source code. That too is really complicated. It is pretty unrelated to your problem though. I still do not know a good solution to that. As is, we have term/goal expansion with 4 arguments that translates both the term and the layout, but writing term_expansion/4 or goal_expansion/4 rules is really hard. Ideally we do that automatically and have the user just define the /2 versions.

1 Like

Yes.

and I have that working for all of my code at the moment, but no doubt it’s incorrect for all DCG terms.

I have custom operators and custom syntax to cut down on verbose repetitive things I’m doing.
The operators and syntax are implemented using other DCG rules, and it’s easier for my brain to stay in one representation rather than bouncing between the two.

Could an idea like the following work? (I realize this actual mock implementation is very broken, and this would be a personal temporary stand-in).

user:goal_expansion(OldGoal, NewGoal) :-
    user:dcg_body_expansion(OldBody, NewBody),
    dcg_translate_rule((a -> OldBody), (_ :- OldGoal)),
    dcg_translate_rule((a -> NewBody), (_ :- NewGoal)).

I don’t really see where this is heading to. Possibly the quite recently added library(macros) can be of help? Else, it would help if you give some examples of the transformations you want to do.