Built-In Preprocessor for context arguments

One key limitation I keep running against is the fixed syntactic argument structure of predicates.

This is in particular the case when establishing a layered architecture and deeper call structures.

Suddenly, I notice I need some information at the very bottom, which needs adding an argument across many predicates / calls – leading to massive change of predicate interfaces.

O’Keefe has a solution, a context argument structure that hides the arguments in one bound variables with “setter” and “accessor” predicates.

He also shows in his book how the overhead can be eliminated using macros (p.314, in the Crafts book) – which i never fully understood – the presentation is very terse.

I am thinking that my pain here is likely prevalent (pervasive?) – wouldn’t it be nice to have a built in capability for variable / extensible / named arguments of predicates with zero costs achieved with macro preprocessing.

There would be two kinds of code:

Code with the usual variables and bindings.
Code that include as variable a structure which is expanded by the preprocessor (or compiler) and thereby hides, takes care of the argument structure expansion, without the need to refactor lots of code.

all comments are much appreciated,

Dan

1 Like

The classical Prolog way is to use an option list as we see with many of the ISO predicates. The pain is that a list of options is relatively slow to process. A faster alternative is to use SWI-Prolog’s dicts, which provide access times that are pretty much comparable to arg/3 as long as the number of keys is small (tens). This gives you more or less what is common practice in many imperative languages: use an argument for the common and changing parameters and a struct/record for the (mostly) read-only context that you can easily extend.

An alternative are the languages that allows for optional and/or named parameters. You can quite easily do that in Prolog as well, as long as you give up the idea that predicates with the same name and different arguments are unrelated. You also need to reserve some syntax for specifying a named argument. Given that, you can invent a declaration for the predicate and let goal expansion create the eventual predicate call based on that declaration (putting the arguments in the right place, filling defaults).

The ECLiPSe version of dicts do something like that. You declare a dict with named parameters (forgot the syntax), now a term tag{k1:v1, k2:v2} is translated into a term tag(a1,a2,a3,...) where v1 and v2 end up in the location defined by k1 and k2 and the other arguments are default. ECLiPSe defines this as read macros, i.e., read/1 does this expansion. In SWI-Prolog you could do this in goal/term expansion.

Using any of these schemas it shouldn’t be too hard to do an automatic program transformation to flatten the whole thing. For the ECLiPSe way you do not need that as it is already done.

The first alternative, using dicts for additional (context) arguments works without any expansion and should typically have no significant overhead. Note that just using additional arguments also implies overhead. It is specific to SWI-Prolog with an easy migration to ECLiPSe though. You can use SWI/ECLiPSe to convert the program to a traditional Prolog program.

1 Like

Thanks …

Perhaps the extended DCG is a way to go, if one can declarative adjust over time the number of arguments present, without a need to change existing code – although, a scheme needs to be envisioned to ensure that new predicates are called when new arguments are given bindings vs. old code that ignores those bindings.

This might be challenging though, unless one can use some kind of aspectual enhancements during compile time – which, i guess, generate guards that distinguish between the old predicates that are oblivious to the new argument, and the new predicates which need them.

Thanks.

I think my use case is a bit different.

Suppose you have a verb deposit(Sum, Account0, Account).

Later I learn that for some deposits I need to add a certain justification code, but not for all.

so, now I need: deposit(Justification, Sum, Account0, Account) as well.

And suppose that deposit /3 is implemented using several predicates, including two of three call “levels” down, log_deposit/2, (and others), which will need the justification added, if its there.

So, now I need to differentiate between two calls:

deposit/3 and deposit/4 and
log_deposit/2 and log_deposit/3

Which basically, requires me to either duplicate and extend deposit/3 and log_deposit/3 to deposit/4 and log_deposit/4 – as well as add the Justification parameter across all calls and have the first deposit/4 be called by a deposit/3 with a default value for justification.

Ideally, I would want a preprocessor handle all of this, while i am only adding the extended version, without the need to touch existing code.

Now, that i think about this it seems that this must be designed upfront like this – not sure if a preprocessor can handle this kind of use case.

hope i described this correctly …

Dan

This is indeed what I do.

Just not via a search and replace, since upfront I don’t know which predicates require extending – this require tracing the call and identifying all called predicates in the body of predicates, where the additional argument needs to be added.

This involves variety of predicates and is not known apriori, until I review the call graph …

What I do know is that arguments are passed along the call graph, and that some can form subgroups for different functional purposes … and that’s were i see potential for a more flexible approach.

I general I am thinking that the syntax of arguments plays a specific role in calls, for distinguishing predicates and indexing. Yet, sometimes, an argument structure that is named or perhaps event simply pushed onto a stack and referred to by location on the stack, is more efficient when it comes to change and evolution of code …

edit:

I now recall that sometimes during prototyping i hacked a solution by changing an argument into a compound – so sometimes the arg is an atom and sometimes a compound, and that’s how the call can be distinguished without the need to add an argument … just that its probably less efficient as an argument passing / binding scheme.

Edit 2:

And, O;Kefee discourages the use of compounds in arguments, due to ineffficiencies – a practice sometimes seen to “type” argument variables.

I think this is your real problem. Where do these levels come from and what is their purpose? Without code, there is no way to show that they are necessary, nor that they are useless; even if we had all the code, experience shows that one could and in fact will always argue that yes, more code is better than less code.

Thanks, Boris.

The levels emerge due to architectural refactoring; in order to ensure a modular code base that is easier to understand, with less code duplication, towards more reuse.

Once you introduce molecularity you also increase parameterized code and more indirection – hence layers in code.

Its something you can’t avoid.

And, indeed programming languages were not built with architectural modularity in mind, so, and hence, there is little support for handing such use cases.

Dan

p.s. object-orientation has some support, of course, but, OO only takes you some distance.

Also, constructions such as polymorphic dispatch, meta programming and closures are also helpful, but these cost dearly in terms of performance.

Best, if there exist preprocessing support that handles such refactoring needs …

Surely not with this attitude :wink:

Seriously now, I do believe that you can and should avoid it. But again, in practice, I have seen that this opinion goes strongly against the grain. As long as we cannot agree on a method for measuring the goodness of software, we can never agree on what is better.

Actually, there is quite a lot of work in the area of assessing what good software is. Lots of work has been done on software metrics and how these affect, development, productivity, defect and maintenance costs.

Software architecture and modularity of code is one key knob in the scheme of approaches.

You can have a look at the many books written by Casper Jones [1].

Incidentally, research I have done earlier on was focused on qualitatively guiding good software decisions – while good meant identifying and selecting the right tradeoffs appropriate for a given business and organizational development context.

[1] https://www.amazon.com/Guide-Selecting-Software-Measures-Metrics/dp/1138033073

I have seen and tried to read some of these books. They make way too many assumptions that I disagree with. If we have some freely available text that you can share I can go in particulars.

One problem with measuring is that you can measure things, but it is impossible to compare; you can attempt to have a control group but in practice impossible to avoid cross-talk. Additionally, you easily end up measuring things that are easy to measure but you readily ignore things that you can’t easily measure. There is of course the whole correlation vs. causality issue.

I hear what you say, but Casper Jones in particular has dedicated his life measure software in numerous studies and I think a lot of his work has proven its validity.

I think software can be complex and embody various assumptions but human cognition and organizational behavior are key bottlenecks that play into this very strongly and consistently.

This is exactly the point.

Good we have an overlap.

But, there is a triangle – known from Activity Theory:

Human <-> Tools <-> Artifact.

And these together act.

So, if code is too complex (artifact) and the tool we use (IDE, Programming Language, etc) has limited support for dealing with change, then cognitively we are making more mistakes.

Hence, code complexity measures are an indicator, all else equal – which often is the case.

Could you please specify, are you making some statement about code complexity in relation to any perceived or measurable goodness of software?

As an example, yes. code complexity has a direct impact on bugs.

Also, on understandability of code – hence, maintainability, hence cost of software, time coders can spend on writing a features implemented, on enhancing software to meet new user needs, hence user experience, etc.

Its all one big spider web …

I might be reading between the lines here, but to me it sounds as if indeed, trying to keep code simpler does have a benefit?

“Make everything as simple as possible, but not simpler.” Albert Einstein

Complexity measures are often indicate code smells --areas where you need to improve things by use of modularity, loose coupling, smaller chunks of functionality, etc.

In short, code is simpler when its architected well …

It’s easy to add a “context” parameter using package(edcg):

:- use_module(library(edcg)).
:- multifile edcg:pass_info/1.

edcg:pass_info(context).
edcg:pred_info(my_pred, 2, [context]).

my_pred(X, Y) -->>
    Context/context,  % Unify Context with the `context` passed argument
    do_something(X, Y, Context.some_field).

This expands my_pred/2 into my_pred/3:

my_pred(X, Y, Context) :-
    do_something(X, Y, Context.some_field).
1 Like

I think regular DCG with semicontext notation, i.e pred, [...] -->
can work just fine if you want an out-of-the-box solution. I’m sure you know that they are expanded to use list differences which are known to be efficient.

Have you seen Triska’s example on implicitly passing states?

He has an example on counting leaves with dcg. In it, normally one would keep a count as argument.
Then he shows how to use dcg to make the count implicit. I suppose you could do the same for complex terms and also use different states to distinguish between types of states you might have.

Otherwise, there’s the eDCG library that Ludemann just posted which I didn’t know about:

Thank you.

Yes, this could be one way to go …

But, it would limit my argument representation to a composite, making the code less “exposed” to indexing, i think.

Also, there is more memory and (i think) compute overhead when passing along composite structures rather than “flat” argument lists.

But, what you suggest is a key approach to handle argument passing in a more object oriented way and i understand there are preprocessing techniques to eat the cake and have it too -:slight_smile: