Wrapping predicates

In JUnit 5 which is one of the standard Unit Test platforms for Java there is a resource allocation example that uses @Before on line 23 to create a collection and then uses @After on line 28 to clear the collection. Because all of the wrapper methods are implemented in one class the methods can pass state via an instance value without passing via the wrapped method arguments.

In general, having a wrapper being aware of another wrapper would be a big, stinky, code smell :nauseated_face: :smiley:

I didnā€™t mean to intend that one wrapper was aware of another wrapper. That would be even worse than you noted; talk about combinatorial explosion and bug hunting nightmares. :infinity:

Just wanted to give an idea for a wrapper that could not be just added based on First Added First Called (FAFC) or Last Added First Called (LAFC) means but that the wrapper would have to consciously be put into the ordering of the wrappers.

But the semantics are not the same. Assume that we have: b1, b2, and b3 before methods plus a1, a2, and a3 before methods for a wrapped method/goal g. We get (order not significant within before and within after methods):

b1, b2, b3, g, a1, a2, a3

But if we have: w1, w2, and w3 _around_methods with b1, b2, and b3 parts and a1, a2, and a3 parts, we get (again disregarding ordering of the around methods):

w3(w2(w1(g)))

i.e. as a sequence, b2, would be called after a1 and so on. Am I making sense? Sleepy and also many years since I read about the origin of this stuff in OO LISP extensions.

I am surprised you are still online after a day of MBT and the time difference.

Am I making sense?

The demo examples were clear, you lost me at, as a sequence, b2, would be called after a1 and so on.

For me if we want to go past just the initial concept Jan noted with tabling, then I am starting to think we should use the 5 or 6 port box model when talking about this.

In the functional world and specifically F# they use the term Railway Oriented Programming with diagrams which makes it easier to understand but those are not as expressive as Prolog with the 5 port model.

If the box model works for this, and I donā€™t see why not, and with the restriction that the wrapped predicates are deterministic, to me this is looking a lot like an enhanced Railway Oriented Programming, but Railway Oriented Programming is not about wrappers but a means of passing exceptions through the chained calls cleanly. If and how that can be translated into Prolog is a bit beyond my skill in Prolog (I think, maybe setup_call_cleanup is the way), but canā€™t learn if I am a spectator.

The nice thing about the diagrams is that it would be easier to convey the idea to new users.

I pushed a first version to the git master. See library(prolog_wrap) for details. I do not consider the interface or semantics final. If you have stuff in mind that could benefit from this, give it a try (or possibly just a thought whether it can work) and shoot.

I intend to see whether it really satisfies my needs for tabling and see whether I can add some debugging tools on top of it.

3 Likes

There were some related past discussions on this topic in the old SWI-Prolog Google group. E.g. Redirecting to Google Groups

But better to start a new thread if you want to discuss that and keep this thread focused on wrapper predicates.

1 Like

Hi Jan,
Definitely this can help me to reduce the overhead that I have in two of my libraries: one is the assertions library and other the run-time checker of such assertions. Basically the assertions library is a port and extension of the assertions language of Ciao Prolog to SWI-Prolog, and it allows us to write assertions about predicates that documents or establish properties, its most basic syntax looks like:

:- [Type] Head : [Preconditions] => [Postconditions] [+|is] [GlobalProperties]

For instance:
:- pred append(A, B, C) : list(A), list(B) => list( C) is det.

Here, the type is pred (that is a bit complicated to explain, letā€™s say that allow to distinguish between different types of predicates), the precondition is that both A and B should be lists, the post condition says that if the execution succeeds, C is a list, and the global property says that if the precondition is true, then the predicate is deterministic.

The run-time checking library provide term and goal expansions that write proper wrappers to the predicate so that we can see if all of the conditions specified in the assertion are satisfied. Actually, what you wrote as implementation of the det/1 property is quite similar to my implementation of the global property no_choicepoints/1, since I defined det/1 as a predicate which has exactly one solution, regardless if it leaves a green choice point. You can see several implementation of wrappers to check other global properties in the library: https://github.com/edisonm/assertions/blob/9bac6d141b538fdea13146c00d4305405e291cbc/prolog/globprops.pl
An important difference I see is that I am not using throw/1 to report the violation of the property I am checking, but a less disruptive predicate called send_message/1, ported from Ciao-Prolog to SWI-Prolog and available in the library intercept.pl
https://github.com/edisonm/xlibrary/blob/0e1ff173333e679e4757c691dd2944a03151707b/prolog/intercept.pl
So basically the call to send_message/1 will cause the call of a handler provided by an upper call to intercept/3 without stopping the execution. The idea is that when you are executing the run-time checker, you should be able to execute the program without interruptions as before, but reporting all the problems found. For instance:

p(a).
p(b).
q(a).
q(b).

r(X) :- det(p()),det(q()).

Calling:

det(r(X).

should report 3 det errors (one for p, q, and r), but if I use throw/1 it will only report the first one found.

Going back to wrappers, the run-time checking library suffer from the same limitations of the :- table directive, so a clean way to enable/disable them will also be beneficial for it. In fact, I copied the wrapper code from the tabling implementation :slightly_smiling_face:

About allowing multiple wrappers with the same name on a predicate, I had a similar problem and is related to simplification of wrappers, to avoid to check the same property twice, for instance to avoid det(det(Predicate)). Basically I removed the duplicated wrappers in an optimization step, which in this specific context have sense, but may be in others will not. Also, the order of wrappers donā€™t matters, the only effect is that the order of run-time check messages will be different.

1 Like

Thanks for the pointers. The signalling interface is interesting. In its usage for this particular problem it relates to the print_message/2 interface SWI borrowed from Quintus. In general of course it is a completely different beast.

I like the overall syntax.

:- [Type] Head : [Preconditions] => [Postconditions] [+|is] [GlobalProperties]

I guess we can combine that with the signal interface to print problems or trap the debugger on problems?

Iā€™m very tempted to move some of this to the SWI-Prolog standard libraries. Together with the dynamic (un)wrapping this could provide some great debugging tools. It would be really nice if I can simply state ā€œstart debugger on failure of p/3ā€ or ā€œstart debugger on p/3 if it produces a cyclic termā€, etc.

One of the problems we were faced with in the past was that the Ciao emulation code is GPL. Seems your new stuff is BSD. It shouldnā€™t be a big issue to avoid the Ciao emulation, no? Iā€™m happy to have a look at that.

Donā€™t worry about Ciao emulation, since a time ago I cleaned up its dependency and now I only use SWI-Prolog, so none of my libraries depend on Ciao emulation anymore. The port to Ciao of predicates like send_message and intercept is only in the semantic sense, since the implementation is completely different. Also, if you compare assertions and rtchecks with the original libraries, they look completely different, since I cleaned up the experimental code that in the current context didnā€™t have sense.

would be really nice if I can simply state ā€œstart debugger on failure of p/3ā€ or ā€œstart debugger on p/3 if it produces a cyclic termā€,

That is the idea of intercept/send_message, for instance, something like:

intercept(Call, det_error(_,_), gtrace).

will cause start gtrace if a det_error is send. Although I guess in the current implementation gtrace will appear in the middle of the det/1 library and not in the code being debugged I guess, I havenā€™t try but if that is the case should be easy to fix.

1 Like

Ciao is LGPL so any code borrowed from it should be LGPL as well. I am not sure if this changes anythingā€¦ W.r.t. the assertion language, semantics of assertions and program with assertions, etc., weā€™d be happy to discuss any issue or provide links to recent papers (although Edison is of course an expert in this topic!). This is an open research problem and our design and implementation are still evolving.

It appears that wrap_predicate interferes with some other things. I discovered this using the new version of pack rdet, which uses wrap_predicate.

  • meta_predicate declarations donā€™t work ā€“ I had to add the ā€œmodule:ā€ annotation to the meta-predicates ā€œGoalā€, which isnā€™t needed if the predicate isnā€™t wrapped.
  • it doesnā€™t wrap some imported predicates: No permission to redefine imported_procedure 'apply:convlist/3'.

The meta-predicate issue is known and will be addressed after holidays or if someone else fixes it first. It is surely correct you cannot wrap an imported predicate. Whether or not that is a bug is less clear (except that it should give a more adequate error). Convenience predicates can resolve the imported predicate or may create a normal Prolog wrapper that only wraps the version imported in some specific module.