Sharing term expansion across modules

Hello,

I have asked this question before [1], but the suggested answer doesn’t seem to work.

I want to create a list of term expansions that can then be imported into, and used within, modules and the “main” user file.

Can this be done?

thank you,

Dan

[1] Term_expansion across several modules - #2 by grossdan

Whats cooking! Some blog about it?

Maybe its then also easier to give advice…

I am using term expansion to demonstrate a high-level domain specific language (DSL). Once expanded terms are processed.

Its somewhat similar to the macro expansions described in the O’Keefe book (p313).

While the DSL is intended for a user file, i noticed that the DSL can be quite useful for modules I developed as well to simplify internal code.

But, i then ran into the problem that in modules DSL keywords don’t get expanded, unless i replicate the term expansion declarations in each module.

I also noticed that if i don’t declare user:term_expansion( … ), the expansions don’t happen in the user file – hence keywords are skipped during processing.

Dan

Place the expansion in user:

If there is no local definition, or the local definition
fails to translate the term, expand_term/2 will try
term_expansion/2 in module user .

https://www.swi-prolog.org/pldoc/man?predicate=term_expansion/2

It will be always applied. Is it broken? What is your test case?

P.S.: You can also add user rules from within a
module, your DSL module, like that:

user:term_expansion(... , ...) :- ...

Thank you.

Yes, i happened to have written the term expansions with user prefix.

I tried all kind of possibilities and it now seems that if i define something like this:

user:term_expansion(keyword1(X,Y), dsl:keyword(X,Y)).

It does expand within a module, but, interestingly only when i consult the module explicitly – when consulting the user file, the expansion in the module doesn’t occur.

Not (yet) sure why.

It does raise in me also the question of the order of expansions – what order would be used when several modules have keywords for expansion and, say, expansion order matters overall.

I am starting to think that using expansions within modules for a dsl is probably not a good idea.

If I understand you correctly, I think the answer is no. You don’t “import” term (or goal) expansions. When a module is loaded, terms in that module will be expanded by looking for term_expansion clauses found in 1) the module, 2) context user, and 3) context system in that order. So there is no “fine grained” control based on imports and exports; the expansion is either local to the module or global (in user or system). Modules can define expansions to be either local or global (as in user:term_expansion(From,To) :- ....

Furthermore, any “global” definitions only apply to code loaded later (expansion happens at compile time). And if you change any term_expansion's, e.g., by reloading the defining module, it doesn’t redo any of the previous expansions. So it pays to get a clear understanding of how this mechanism works dynamically.

That’s my understanding but I’m a bit of a novice in this area. It would seem to explain what you’re observing, yes/no?

2 Likes

Thank you.

Indeed using the word “import” is a bad choice of term.

Although a module can, apparently, define (“export”) a “global” expansion within the user space.

I guess macro expansions, to declare program wide, across module, DSLs doesn’t really work in Prolog – i guess in languages such as Lisp and Scala – it does work.

Dan

You’ll have to get a little more specific about how you’re using your DSL. Clearly if I define a global expansion and load it first, that expansion will be “program wide”. What you can’t control is what modules/files it will be applied to.

Or you can divide your program in two, i.e., pre and post expansion definition. But this all seems a bit contrived.

I think, per expansion processing rules, a user expansion would (or should) be applied to all modules, as they are consulted. So, the expansion should apply everywhere.

re: specifics

Consider for example a language that allows specifying code blocks such as a “with” statement in VB.NET [1], which enables writing code without need to repeat an object expression – or, to take another example from logtalk – a scope declaration for an object description.

in both cases you want to have block of code, such as so:

begin_block(term_x).
keyword1(a,b,c).
end_block.

Internally, such block could be processed to mean language_term(term1(a,b,c,term_x)).

To get to that, one needs to process expanded terms for begin_block/1 and for end_block and keyword1.

Implicit there is also an ordering if two or more such blocks are declared and when the declaration of a term_y depends on the existence of a prior declaration term_x.

Now, what if term_x would be declared in a module and term_y in another module or user file – the problem then is to ensure correct ordering of expansion, as they occur during consult – which, i guess, is a level of control not specified in prolog – although, it might implicitly be based on some explicit or implicit module import dependency graph.

Dan

Edit:

But, if a user scoped term expansion is declared in a module – then knowledge of its existence seems to happen only after the module is consulted – so, ordering of module consult would, in such a case, affect expansion processing – as you have indicated …

So the programmer should ensure that a module that declares a DSL that should be usable across to program is consulted as early as possible – hence, how :-use_module are declared relative to the user file would be crucial.

[1] With...End With Statement - Visual Basic | Microsoft Docs

Pretty much correct. If you want to use term expansion across modules you have some options. One is to define them in user and use e.g. prolog_load_context/2 to find the module or other relevant context to decide whether to expand or not. For example, this general rule can check that your DSL module was imported into the current module. The alternative is define the rules locally in each module where you want the expansion to happen. You can define the rules in some module and than merely add a link clause to make the rules active in a specific module, e.g.

:- use_module(mydsl).
user:term_expansion(In, Out:) :- mydsl_expansion(In, Out).

And you can even hve mydsl define a rule in user that expands importing the module into the two terms above, so it all looks clean.

One day that will probably change. I did a lot of work on a more module friendly new design, learning from how other systems did a better job, but the project stalled.