-
Notifications
You must be signed in to change notification settings - Fork 134
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Goal expansion in lib reif #2433
base: master
Are you sure you want to change the base?
Conversation
Thank you a lot for working on this, I greatly appreciate your interest in improving There may well be mistakes in the underlying expansion mechanism of the Prolog engine itself, and such experiments are well suited for finding such issues. As one immediate comment on the PR itself: Could you please separate the documentation changes from the goal expansion changes? The documentation of |
One important test would to check whether the expansion is correctly applied: You can do that by using :- use_module(library(clpz)). :- use_module(library(format)). :- dynamic(p/2). p(X, Y) :- X #< Y. Yielding: ?- listing(p/2). p(A,B) :- ( integer(B) -> ( integer(A) -> B>=A+1 ; C is B, clpz:clpz_geq(C,A+1) ) ; integer(A) -> D is A+1, clpz:clpz_geq(B,D) ; clpz:clpz_geq(B,A+1) ). true. |
Thanks,
|
The expansion in the cited source (happens to) work for SICStus. The variation for SWI works for the examples in the paper. It fails to expand more complex examples. This is partially due to the incompatibilities of module systems. I am not very fond of the expansion code as such and I hope that we might find a cleaner way of doing it which might provide a mechanism that can be used in other code as well. One particular aspect that should be covered is the exact ISO semantics (and thus their errors) of term-to-body conversion. In any case, the text of Scryer's |
I think it would be good to start with restoring the canonical |
Funny thing this is how I actually started work on this task, I first modified reif.pl from Scryer to look as close as possible as reif.pl from the original source, and then after inspection I converted it back ;) |
|
Thank you all for help, the problem was that expansion wasn't correctly applied because of With the latest commit it seems to work well, here are some benchmarks:
I will remove my debug code, maybe write some other benchmarks and this PR is now ready for a discussion on how to wrap it up. Questions:
|
Thank you so much for working on this, and congratulations on these awesome results! Regarding the remaining questions, my personal opinion:
|
There are a lot of unclear things related. Like:
|
Further,
|
I think it might be possible to implement runtime toggle for strict ISO conformance for certification for example, but give users a relaxed default. I'm not sure how this proposal goes with Scryer Prolog spirit though. |
As for conformity there is an extension mechanism (5.5, 5.1 e). The problem behind is that the update semantics of SICStus prevents many optimizations that should remain user transparent (like inlining/unfolding simple goals), and at the same time does not prevent inconsistencies due to updating goal expansion. Ideally some larger chunks could only be updated together in one fell swoop. Or somehow dependencies are registered that enable a clean recompilation. |
This is needed for benchmarking library(reif) and its newly provided goal expansion (mthom#2433). It partly addresses mthom#321.
I have added a preliminary version of |
This is needed for benchmarking library(reif) and its newly provided goal expansion (mthom#2433). It partly addresses mthom#321.
0bd3d7a
to
bfd52f9
Compare
This is starting to look very good, very closely following the master copy of I still note the following differences that I think can safely be eliminated by using the original code in these places: 157,160c161,162 < ; nonvar(T) -> throw(error(type_error(boolean,T), < type_error(call(If_1,T),2,boolean,T))) < ; throw(error(instantiation_error, < instantiation_error(call(If_1,T),2))) --- > ; nonvar(T) -> throw(error(type_error(boolean,T),_)) > ; throw(error(instantiation_error,_)) 224,226c226,227 < ; nonvar(T) -> throw(error(type_error(boolean,T), < type_error(call(If_1,T),2,boolean,T))) < ; throw(error(instantiation_error,instantiation_error(call(If_1,T),2))) --- > ; nonvar(T) -> throw(error(type_error(boolean,T),_)) > ; throw(error(instantiation_error,_)) |
I never knew what to put into the last argument of P.S. I have much less free time than I thought, I'll try to finalize this PR soon. |
Thank you a lot, this is starting to look very good! To make the changes easier to track, could you please, in a final step, reduce the changes to their essence? I think a few commits would suffice, such as:
This would make it very easy to follow the development, also later. I think an easy way to merge, drop and reorder existing commits is to use |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure it makes sense to produce such ad-hoc extra information. If, at all, it would make sense to have here a pair-list for various extra information. But this hard-coded version does not fit into the remaining setting
9c5bdb1
to
07962ab
Compare
I think it is ready for a review. I don't know if I need to write more tests, I like the idea of property testing, but I will need to write the whole framework to do it correctly, and it is way outside of scope of this ticket. There is also room for improvement as not all instances of Original memberbench.pl also works, with just minor adaptations, but it doesn't fit into Scryer's benchmark framework. Edit: What to do with the last argument to |
07962ab
to
0db03a5
Compare
src/lib/reif.pl
Outdated
/* | ||
no !s that cut outside. | ||
no variables in place of goals | ||
no malformed goals like integers |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you a lot for working on this!
I understand these changes are meant to address the first point above? What about the other two issues though, for example, what if 1
occurs at the position of a goal, as in (a,1,b)
? A faithful expansion must preserve the exact behaviour we would observe without the expansion, is this ensured?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I think I've fixed all those issues, maybe I need to write more test cases, but I test at least the following code:
\+ cut_contained((a,b;1), _)
That means that cut_contained/2
must fail thus causing goal expansion mechanism to skip expanding current if_/3
041f429
to
cda8406
Compare
Regarding this point: Personally, I think it is best structured as: (1) one commit that introduces the canonical What would not make sense, at least in my opinion, is to introduce multiple commits with back-and-forth changes of approaches and already obsolete and superseded changes into |
What exactly is the property we are interested in? Maybe whether it contains, or might contain, |
Personally, I see no issue with this, at least not in this case: This exception term is locally thrown and caught, with no possibility of interference into or from application code that throws a ball with the same shape. |
Personally, I think there may be a better location, and also better name if we can find out which property is actually essential here. From the comments in the original |
tldr version: I don't know in general case is it safe to skip a particular goal expansion even if I've detected cut in it in a callable position. I wanted to solve it in loader module, to universally prevent unsafe goal expansions that might expand scope of foo:goal_expansion(maplist(A, B), X) :- ... . Here the goal expansion expands It would be nice to mark some expansion as "inline only" – this information will be enough. |
Also wrapping goal in a foo(foo(foo(!);foo(true;true))). I need to recursively descent into it while verifying every meta-predicate declaration and possible module specifiers and whatnot. It becomes quite hairy, because goals can be arbitrarily complex. I still want to solve this problem, but I decided to have at least something earlier :) |
Maybe it helps a bit with this: The goal is to do goal expansion of |
It seems this is a candidate for an improved expansion mechanism, which we need in any case? (#2532) |
cda8406
to
0b6ec52
Compare
I've rebased everything onto the latest master and squashed some commits and I left only commits which add distinct functionality |
ee6ec2a
to
9000258
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
?- cut_contained(1,G_0).
false.
?- cut_contained((1->true),G_0).
G_0 = (1->true).
?- cut_contained((1,true),G_0).
false.
How does this fit together? Name, documentation, and behaviour
I agree, name isn't good. The idea behind it is following:
I will fix an example provided by you, it is because I ignore left argument of What is the good name for it? Maybe something like |
Please reconsider your approach: This ad hoc fixing leads to the creation of niches for bugs to reside — until they hit. I believe that it would be better to perform the expansion always, and then take another (general) step to remove all those unnecessary |
Ok, I think I understand what you mean. Basically to remove |
I think it can be solved using goal expansion (after fixing unfortunate safe_goal(G) :- goal_sanitized(G, G).
user:goal_expansion(call(G_0), G) :-
loader:complete_partial_goal(0, G_0, _, [], G),
safe_goal(G).
user:goal_expansion(call(G_1,A), G) :-
loader:complete_partial_goal(1, G_1, _, [A], G),
safe_goal(G).
user:goal_expansion(call(G_2,A,B), G) :-
loader:complete_partial_goal(2, G_2, _, [A,B], G),
safe_goal(G).
user:goal_expansion(call(G_3,A,B,C), G) :-
loader:complete_partial_goal(3, G_3, _, [A,B,C], G),
safe_goal(G). |
I have fixed examples that were provided. It was mostly because I didn't validate some subterms. Regarding latest note about call/1 and call/2 safe elimination applied globally. I will take a look into it. This probably will mean discarding a lot of changes, but overall it should be a more clear design. |
5424bab
to
6eeac07
Compare
Direct copy from: http://www.complang.tuwien.ac.at/ulrich/Prolog-inedit/sicstus/reif.pl This commit doesn't compile, it is here just to have a clear set of differences with the source.
* predicate_property/2 removed, because it isn't working (mthom#2443) * meta_predicate instruction was split (mthom#2444) * Few changes were adopted from SWI variant[1], because it has similar handling of (:)/2 with predicates in modules. * Scryer specific changes [1]: http://www.complang.tuwien.ac.at/ulrich/Prolog-inedit/swi/reif.pl
Benchmarks compare performace of memberd/2 with and without goal expansion to memberchk/2. Unit test mostly capture the current behavior, do some sanity checks, couple of corner cases (like handling of cyclic terms) and property test of tfilter/3 and tpartition/4.
As a part of optimization goal expansion in library(reif) inlines Then_0 argument verbatim into predicate body – this avoids unnecessary call/N invocations and dramatically increases performance, but not all goals are safe to be inlined in such a way. Here we are skipping this optimization if !s or invalid goal were detected to prevent undesired side-effects from leaking into outer goals.
6eeac07
to
5869513
Compare
I've played a little bit with universal call/N elimination and it works in most cases, but I think it is major modification of what is and what isn't expanded and it far exceeds original scope of task marked as "good first issue" ;) So I moved this task to "draft" state again. Hopefully in future months I'll have more time to think about it. |
I've searched for a task that is marked as "good first issue" and can be done purely in Prolog, and I think issue 977 was the only one, but it is already claimed. I've reached out to @sblaplace and it told that it is Ok, we can continue to work on it together or me alone, I'm open for any combination of work.
Now, let me describe current state. Currently it is a draft and it has debug code which I will remove.
What was done:
goal_expansion/2
and predicates that it relies upon were copied from the source1.statistics/2
predicate, but it also needs to be written in Rust and I don't know it. Benchmark can be run from shell command line:predicate_property/2
invocation with an exception. Another task shall be created for that predicate, it also requires some Rust knowledge.reif:testall
to run them. I can remove them if they don't comply with module guidelines.And now the most important point, after all of this I didn't observe any noticeable performance improvement! Am I doing something completely wrong?