PROLOG-REQUEST@SU-SCORE.ARPA (Chuck Restivo, The Moderator) (06/07/86)
PROLOG Digest Monday, 9 Jun 1986 Volume 4 : Issue 17 Today's Topics: Query - TRO & Micros, Application - Income Tax Planning, Implementations - Assert & DFID & Standard Behavior & Tricks ---------------------------------------------------------------------- Date: 3 Jun 86 19:59:24 GMT From: MorrisseyTJ <tim@ucbvax.berkeley> Subject: TRO in C-Prolog Has anyone implemented TRO (tail recursion optimization) for C-Prolog? -- Tim Morrissey ------------------------------ Date: 6 Jun 86 02:12:05 GMT From: FJ Hirsch <fjh@ucbvax.berkeley.edu> Subject: prolog for ibm pc? I am looking for information about Turbo Prolog or other implementations of prolog for the IBM PC (AT&T 6300). Pointers to reviews or personal experiences would be appreciated. Thank you. ------------------------------ Date: 3 Jun 86 14:46:00 GMT From: pur-ee!uiucdcs!uicsl!gooley@ucbvax.berkeley Subject: looking for Prolog UNSW Prolog is *not* a variant of C-Prolog. It was developed independently,has a slightly different syntax, behaves quite differently in some situations, does not try to fake a tagged architecture, lacks many bells and whistles, and (according to a letter in a recent issue of "Computer Architecture News" [the ACM SIGARCH bulletin]) is about half as fast when run on a VAX-11/780. Its chief advantages are simplicity, modularity (relatively easy to modify for instrumentation, except that the source lacks comments), and portability. It's my opinion that it will run on any 32-bit machine under UNIX with only trivial changes, and, with a little work, on anything that has a C compiler (I ported it to our Gould PN9050 and had only to change a few pathnames). ------------------------------ Date: 3 Jun 86 18:13:28 GMT From: MorrisseyTJ <ihnp4!drutx!druhi!tim@ucbvax.berkeley> Subject: "assert" considered harmful? In article <1229@lsuc.UUCP>, dave@lsuc.UUCP writes: > Saumya Debray mentioned recently on the net > that good Prolog programmers don't make much use of > assert and retract. [text deleted] > When I look at the logic, I find it's doing the same > analysis over and over for certain legal conclusions > that are really "facts" for other rules to deal with. > [text deleted] > Once I've determined that T1 controls T2, should I > "asserta" that as a fact, so it no longer needs to > take much time? [text deleted] Is this an example of lemmas? I would like to believe that a formal mechanism for managing and applying previously proved goals could significantly improve the speed of large programs. However, I do see many issues like: - knowing when the lemmas are no longer valid - making the time cost cheap enough to cause overall speedup - keeping the space cost "low enough" - knowing when *not* to store lemmas (I/O, external conditions) Although it is easy to misuse assert and retract, I think Prolog is very valuable as a database language. Databases for practical applications can easily have dozens of relations and change very often. Something I find dearly missing from Prolog are uniform semantics for side-effects. It seems "ugly" that applications and even Prolog support code use predicates like: set_xxx(Value) get_xxx(Value) or add_xxx(Key,Value) find_xxx(Key,Value) del_xxx(Key) Just some food for thought. -- Tim Morrissey ------------------------------ Date: 28 May 86 20:22:00 GMT From: pur-ee!uiucdcs!uiucdcsb!goldfain@ucbvax.berkeley Subject: Income Tax Planning Your "aggregate" predicate looks pretty clever to me ... it certainly uses some of the deeper concepts of Prolog (functor-building and calling, etc.) I can suggest one improvement in elegance, since you mention you wrote a new aggregate predicate for each number of arguments. You could rewrite aggregate to always expect three arguments, the first is the "Goal" as before, the second "ArgList", and the third "Aggr" to hold the result. Then you could use this form for goals with any number of arguments: aggregate(Goal, ArgList, Aggr) :- append([Goal | ArgList], [Amount], Funct), Z =.. Funct, findall(Amount, call(Z), List), listtotal(List, Aggr). I haven't actually tried this code, but something close to it should work. I am using UNSW Prolog, here. As to your other question, here is some code that works for me. This is the function you want: group([a, b, c, d], X)? returns X instantiated to the list of all subsets of [a, b, c, d] excepting the null set, singleton sets, and the set [a, b, c, d] itself. groups([], []). groups([H | T], Result) :- length([H | T], Full), powerset([H | T], Sets), filter(Full, Sets, Result). This takes a set (written as a list) as its first argument and returns the set of all NONEMPTY subsets of it in its second argument. Example: powerset([a, b, c], X)? gives : X = [[c], [b,c], [b], [a,c], [a,b,c], [a,b], [a]] argument. Warning: it returns with every member of the power set EXCEPT the null set. (It does return the set itself as one of the subsets, unless the set itself is the null set.) powerset([], []). powerset([H | T], Result) :- powerset(T, Set1), include(H, Set1, Set2), append(Set1, Set2, Result). This builds a new "powerset" from a new element and a "sub-powerset". include(Item, [], [[Item]]). include(Item, [H | T], [[Item | H] | Rest]) :- include(Item, T, Rest). A very-often used predicate, which should be pre-defined. append([], X, X). append([H | T], X, [H | Rest]) :- append(T, X, Rest). The "powerset" routine returns too much for our purposes, so we filter out singleton sets and the whole set. filter(_, [], []). filter(Full, [Set | More], Result) :- length(Set, 1), !, filter(Full, More, Result). filter(Full, [Set | More], Result) :- length(Set, Full), !, filter(Full, More, Result). filter(Full, [Okay | More], [Okay | Rest]) :- filter(Full, More, Rest). ------------------------------ Date: 28 May 86 14:21:47 GMT From: Micha Meier <!micha@ucbvax.berkeley.edu> Subject: Depth First Iterative Deepening Some points to a possible implementation of DFID search in Prolog: 1] The DFID algorithm always finds the solution; so does a useful Prolog program, unless it goes into an infinite loop. This means that DFID can never find a solution in a shorter time than the usual Prolog: either Prolog's depth-first search is faster or Prolog fails to find anything and it loops. The same holds then for a parallell implementation of DFID, it could only be a way to prevent infinite loops in a better time than a serial DFID rather than speeding up the search in comparison with Prolog. 2] DFID always finds the optimal solution, the usual Prolog finds *any* and maybe all solutions if necessary. If the user wants to find all solutions and the usual Prolog loops, DFID will loop as well; therefore the use of DFID could be only restricted. 3] As Max says, the predicates would have to be divided into two groups, ordered and unordered; this would be the case in any non-sequential processing of the subgoals. If we allow ordered predicates to call the unordered ones and vice versa then the distribution of processors in a parallell execution is no more so easy. There is maybe a simple solution to this problem, otherwise one direction of calls between the two classes of predicates must be forbidden - is it a significant restriction? -- Micha ------------------------------ Date: 28 May 86 14:57:47 GMT From: Max Hailperin <!max@ucbvax.berkeley.edu> Subject: Depth First Iterative Deepening Micha's comments aren't all wrong, but they certainly aren't 100% correct either. The big point he misses is this: If there is a branch of the tree to the left of the solution that is much deeper than the solution, but still finite, DFID is more efficient than depth-first search. This -- not questions of completeness -- is the argument for DFID which interests me. Naturally this case only arises in some class of programs, but that's the point -- I'm curious whether that class of programs is interesting. The point about ordered and unordered predicates calling each other is a bit confused. In reality there is no problem. If an unordered predicate calls an ordered predicate, and that ordered predicate fails because its execution was truncated, some solutions to the unordered predicate are lost -- but this doesn't matter. If an ordered predicate calls an unordered predicate, and the execution is truncated, the outer ordered' predicate fails, because of the general rule I proposed in my last note. Thus the right thing always happens. Much of the time all the programmer wants is one solution, any solution. In this case it doesn't matter whether it is the leftmost or shallowest (= "optimal"). If we can get the shallowest faster than the leftmost (or if they are the same, and we can get it faster by looking for the shallowest), than that's good. Naturally if you really want all solutions, DFID has nothing to offer. This can be viewed as a consequence of my execution rule for bagof. By the way, the proposed modified Prolog does not offer one control structure DFID can support that has been proposed in the context of other parallel Prologs: an unordered, one-solution predicate. ------------------------------ Date: 28 May 86 12:38:26 GMT From: Max Hailperin <!max@ucbvax.berkeley.edu> Subject: Depth First Iterative Deepening George Van Treeck raised the interesting question of depth-first iterative-deepening (DFID) search's applicability to Prolog execution in general and parallel execution in particular. A quick check with the gurus around here (ECRC is a major center of Prolog activity) came up with no knowledge of any existing literature on DFID and Prolog. But of course there might be some. What follows are my own comments, in the absence of any literature. DFID could be integrated into a Prolog interpreter. The language it interpreted would have to be a bit modified, as the standard Prolog control structures and side-effect primitives wouldn't work. The smallest possible change in language definition (I think) would be: - declaration of ordered vs. unordered predicates - cuts only allowed in ordered predicates - no side-effects (assert, retract, I/O). The three non-obvious points in the design of such an interpreter are - negation as failure - bagof - ordered predicates. The implementation of these three features has to take as its guiding rule: truncating the search at a particular depth may only eliminate solutions, never add any. Concretely this means that when executing not(X), you have to keep a record of whether the cutoff-depth is reached in executing X. If so, then not(X) should fail even if X failed. This is because X might have succeeded given a greater cutoff-depth. The same technique is necessary for bagof: if it is possible that not all members of the bag were found, the bagof should fail. Similarly, if a clause of an ordered predicate fails, but had been prematurely truncated by the cutoff-depth, the whole predicate should fail, even if clauses remain. The next question is whether DFID would do any good. In today's Prolog programs it wouldn't, because the average branching factor is extremely low (between 1 and 2). The graph in the article in Artificial Intelligence shows quite clearly how big a loss DFID search is with such a low branching factor. Moreover Prolog programmers are very clever about always putting the easiest clauses first, and also use non-logical constructs to direct the search. Prolog execution is not a brute-force search in a well-balanced tree. So for normal Prolog programs, DFID would make the execution slower rather than faster. Whether the class of programs no one writes with today's Prologs but would write if they had a DFID Prolog is an interesting class of programs is an open question. Note, however, that DFID can also be used for only those portions of a program which would benefit from it. There have also been proposals for adding heuristic control information to Prolog's search. This would encourage programmers to use Prolog's built-in search rather than programming their own. Thus some amount of optimism about DFID's applicability may be justified. Finally comes the question of parallel execution (my own specialty). *If* DFID were useful (see the paragraph above), or in fact if it even came close to the performance of normal depth-first search, then a parallel version could be a practical way of exploiting parallel processing for logic programming. For example, if we could find interesting programs that a DFID Prolog executed at half the speed of normal Prolog, then by using 20 processors we could hope to get an order-of-magnitude speedup over normal Prolog. This possibility is so interesting because DFID has an extremely easy, low overhead parallel version. The idea is that each depth-first search is still sequential, but the search for the proper depth is done in parallel. This "DFPD" (depth-first parallel-deepening) strategy might for example have processor A search to a depth of 1 in parallel with processor B searching to a depth of 2. As soon as A is done, it starts searching to a depth of 3. In general: each time a processor is free, it starts a depth-first search with the next untaken cutoff-depth. This has a much lower communication and coordination overhead than OR-parallel Prologs. Returning to my original caution: DFPD is only a speedup over DFID, *not necessarily* over other forms of search. With a finite number of processors, there will be many realistic programs (virtually all of today's Prolog programs) for which even a normal single-processor depth-first search will be faster than DFPD. (In the infinite-processor case DFPD can be no worse than depth-first search.) Thus this is only a useful use of parallelism if we write programs for which DFID performs at least comparably to depth-first search. Conclusions: 1] a DFID Prolog is easily implementable, though not for normal Prolog. 2] today's Prolog programs would not benefit from DFID (in fact, they would suffer) 3] it is possible that conclusion 2 can be changed (particularly if Prolog is extended to accept heuristic control information) 4] if conclusion 2 were changed, generalizing DFID to DFPD would be an easy way to make use of parallel processing (actually it's not necessary that conclusion 2 be changed -- DFID need only approach depth-first search in efficiency, not surpass it) ------------------------------ Date: 2 Jun 86 19:05:50 GMT From: decwrl!vantreeck@logic.dec.com@ucbvax.berkeley Subject: Flames about a DFID question >1] The copious use of cut in PROLOG indicates that >programmers are frequently interested in only a >single, first, solution. I thought perhaps DFID >might be a way of finding a single, first solution >in very large PROLOG search spaces. >For example, a program to synthesize new chemicals >might be find DFID useful for finding the shortest >synthesise pathway (very large branching factor in a >very large search space). The last note wasn't clear about finding solutions with DFID. I didn't mean to imply that DFID would only find one solution. DFID could easilly backtrack and find the next most least cost solution. I just meant that it might be a quicker way to find that first single solution in cases cases where the search space is very large. --George ------------------------------ Date: 2 Jun 86 18:10:43 GMT From: decwrl!vantreeck@logic.dec.com@ucbvax.berkeley Subject: Flames over a DFID question >(I suspect anyway that this is another of George's >attempts to bring flames into the network, right? :-) No! I was just asking questions. I don't understand why asking if an algorithm (DFID) is applicable to parallel PROLOGs should be construed as an attempt to raise tempers. My reasons for asking were: 1] The copious use of cut in PROLOG indicates that programmers are frequently interested in only a single, first, solution. I thought perhaps DFID might be a way of finding a single, first solution in very large PROLOG search spaces. For example, a program to synthesize new chemicals might be find DFID useful for finding the shortest synthesise pathway (very large branching factor in a very large search space). 2] I've heard logicians complain that PROLOG doesn't adhere to logic because the order of rules should not affect whether a proof is found, e.g., sometimes PROLOG programs go into an endless recursion if the rules aren't ordered correctly. I never expected flames because an algorithm like DFID might search and find a proof regardless of the ordering of rules (was that the flame?). I thought a predicate called 'dfid/1' could act sort of like 'call/1' where the proof of dfid would use a different search strategy (assumes there is no order dependent code in the proof of DFID). I'm ignorant about parallel or concurrent implementations of PROLOG and was just seeking enlightenment -- not flames. Thank you for the thoughtful replies; they were very helpful. The replies seem to indicate that most PROLOG programs have a very small branching factor which would make DFID more of liability than an asset. -- George Van Treeck DEC ------------------------------ Date: 1 Jun 86 23:10:42 GMT From: Tom Blenko <hplabs!sdcrdcf!burdvax!blenko@ucbvax.berkeley> Subject: Standard behavior? In article <1163@sicsten.UUCP> lhe@sicsten.UUCP (Lars-Henrik >Eriksson) writes: >In article <1021@watdragon.UUCP> rggoebel@watdragon.UUCP >writes: >>I don't believe that cut, var, and nonvar cannot be >>described logically, just because they aren't in Prolog >>implementations. >The reason why cut, var and nonvar cannot be "described >logically" is that they are non-logical (or meta-logical, >if you wish) primitives, >that is primitives used to control the search for proofs. The meaning of these primitives are dependent of the particular way an implementation looks for proofs. With a different implementation you could be forced to give a different meaning to cut, var and nonvar, or even find that they couldn't be given any meaning at all. I disagree with the latter comment. If call() and negation -by-failure are provided, I claim that you can define if-then-else() (with your favorite syntax, say P->Q;R), and that if-then-else() is more expressive than cut(). In particular, you need a scoping construct for cut() to make it as expressive as if-then-else(). It is a short exercise to show that that var() and nonvar() can be expressed using call() and cut() (or if-then-else()). So the discussion reduces to establishing the logical or non-logical character of call() and negation-by -failure(). I will speculate that the meaning of call() can be captured by a suitable application of first-order logic, due to its definition in terms of constructive proofs (I have something along the lines of Perlis' AIJ (1985) article in mind here). I am side-stepping the problem of incompleteness of logic-programming interpreters w.r.t resolution. -- Tom ------------------------------ Date: 2 Jun 86 14:00:24 GMT From: Saumya Debray <debray@ucbvax.berkeley.edu> Subject: Standard behavior? (semantics of nonlogical primitives) >In article <1021@watdragon.UUCP> rggoebel@watdragon.UUCP writes: >>I don't believe that cut, var, and >>nonvar cannot be described logically, just because they aren't >>in Prolog implementations. > >The reason why cut, var and nonvar cannot be "described >logically" is that they are non-logical (or meta-logical, >if you wish) primitives, that is primitives used to >control the search for proofs. Perhaps you mean "... cannot be described logically using first order predicate calculus". >The meaning of these primitives are dependent of the >particular way an implementation looks for proofs. With a >different implementation you could be forced to give a >different meaning to cut, var and nonvar, or even find that >they couldn't be given any meaning at all. If the constructs are in any way "understandable", you ought to be able to find mathematical models for them. In the case of "cut", "var" etc. there's a good chance these won't look like the Herbrand models we Prolog hackers are accustomed to, of course, but that's rather different from saying that they couldn't be given any meaning at all. There has, in fact, been some work on giving the semantics of Prolog (i.e. textual order on literals and clauses + Cut) using classical denotational semantics, in terms of continuous functions over CPOs. References are: - Jones & Mycroft, "Stepwise Development of Denotational and Operational Semantics for Prolog", Proc. 1st ISLP, Atlantic City, Feb 1984. - Debray & Mishra, "Denotational and Operational Semantics for Prolog", Proc. Conf. on Formal Description of Programming Concepts, Lyngby, Denmark, Aug 1986 (to appear). -- Saumya Debray ------------------------------ Date: 27 May 86 23:03:59-EDT (Tue) From: Zerksis D. Umrigar <Zerksis%sutcase.bitnet@WISCVM.WISC> Subject: Hacker Tricks Some hacks of dirty Prolog which come to mind... a] Creating a term with fresh variables - assert and then retract it from the database. Depending on your Prolog implmentation, it may be slower than ripping the term apart (using =..) and sticking in fresh variables. b] Using lists and search-trees with unbound variables representing the empty list/search-tree. Some time ago, I ran tests on DEC-10 Prolog to compare this with cleaner structures (using [] and say 'empty'). I found that the cleaner code was better in both memory and time! Hence this is definitely a useless hack. However using unbound tails for difference-lists is definitely worthwhile. c] The following is a rather special purpose hack, but may be useful. Given variable-free terms, one needs to keep track of which equivalence classes they belong to. The user asserts equality (say a=b), to make the program record the fact that terms 'a' and 'b' belong to the same equivalence class. The u ser can also query equality (say a?b) to ask the program whether 'a' and 'b' belong to the same equivalence class. The solution is to store the name of its equivalence class along with each term - however, use an unbound logical variable for the name of each equivalence class. When an equality is asserted, use Prolog's unification to bind the two unbound variables representing the names of the eq. classes together. To query equality, use == to check whether the names of the two eq. classes are the same. In the worst case, linear reference chains would be created, resulting in time linear in the number of asserted equalities when answering an equality query. This compares with lg(n) time for Tarjan's Union-Find algorithm. However, the above method is extremely simple to program in Prolog, the worst-case may not occur too often for a particular application and the constant associated with the above time estimates will probably be smaller than for an implementation of Tarjan's algorithm. d) Thinking of the lack of occurs check as a "feature" rather than a bug of Prolog (there is a paper by Colmerauer on infinite trees in Logic Prog. ed. by Clark & Tarnlund). The best application I found of this "feature" was to create recursive (looping) binding environments when writing an interpreter for Henderson's Lispkit Lisp. Finally, a question, possibly related to (d). Prolog seems ideally suited to manipulate tree-structured data. Is it possible to handle doubly-linked structures? - one in which one can access equally easily a "parent" from its "child" and a "child" from its "parent". I can use unification without the occurs check to create such structures. However, in the absence of destructive assignment in Prolog, updating part of the structure seems to imply updating the entire structure. I find that rather unsatisfactory. One can get around this problem by using lists with unbound vars as tails (as in b) to hold the lists of "parents" and "children" of a node. However, deleting a node gets messy: my solution is to use a logical var. as a boolean - unbound = 'not deleted' and bound = 'deleted'. Is there a cleaner solution? ------------------------------ End of PROLOG Digest ********************