Article View: comp.lang.prolog
Article #520Re: Fun. vs. Logic
From: Pat Chkoreff
Date: Wed, 22 Nov 1989 07:27
Date: Wed, 22 Nov 1989 07:27
330 lines
14788 bytes
14788 bytes
>> patch@hpldola.HP.COM (Pat Chkoreff) writes:
> ok@mudla.cs.mu.OZ.AU (Richard O'Keefe) writes:
>> Most Prolog predicates implement functions, so why not be honest and just
>> use a functional language?
> A function is a relation, so what's dishonest about using a relation?
> Don't laugh: but one of the things I like about Prolog is how easy it
> is to have a procedure return more than one value without having to
> make a special data structure to hold the values and without having
> to use special syntax.
Admittedly, things like divide(X,Y,Q,R) are nice, and it's nice to just use
append/3 instead of ldiff, append, and all those "functional" variants of
the same thing. Absolutely. But here's what ticks me off about Prolog:
the danger of NON-TERMINATION is constantly rearing its ugly head. I write
a predicate, and then I immediately see how it is riddled with dangers when
I call it in certain ways. I'm not even talking about dangers due to the
lack of the occurs check: there's not a thing I can do about those.
I can either document these dangers in comments, mode declarations, or some
other "bag" on the side of the language, or I can rewrite my predicate to be
robust. I, a self-styled purist, usually opt for the latter.
> So use functional syntax in your Prolog programs. No-one's stopping you.
> I would point out, though, that I have _often_ found that the discipline
> of taking something that I initially thought of as a function, trying to
> express it in logic, and then asking "is it possible in principle to use
> this more than one way around" has led me to a simpler and more general
> interface.
Exactly. That's what I do. And I end up with omni-directional predicates.
But in order to do that, I have to come up with some really strange
representations for objects. I suspect that these representations have a
lot in common with what is known as "constructive mathematics". To build an
object, you start with []. Given any object, there are a FINITE number of
constructors you can apply to create another object. My domain predicates
for these objects always take the form:
object([]).
object([C|X]) :-
object(X),
construct(C, X).
The construct/2 predicate always returns a ground term for C. Furthermore,
if X is a ground term representing an object, then C will be a ground term
representing a constructor applied to that object, and there are a FINITE
number of solutions for C.
What does all of this mean? It means that if I invoke the query:
object(X), write(X), nl, fail.
then the interpreter will systematically generate all possible objects in
the domain. Of course, the objects must be recursively enumerable, but that
goes for anything you can represent in a computer.
>> It is very difficult, if not impossible, to
>> write Prolog relations which work robustly in "all directions".
> The simple reason for this is that many predicates have infinitely many
> solutions in some directions. But I fail to see why the fact that a
> predicate can't be used ALL ways round is a reason for only letting me
> use it ONE way round. Why not let me use it in all the modes where it
> does work?
But how do you determine the modes in which it works, how do you document
them in the language, and what does it mean to the compiler? What would you
end up with? A sort of hybrid functional/logic language? GREAT! I'd fill
out a purchase request for that.
By the way, I'll bet that a lot of predicates which SEEM to have infinitely
many solutions in some directions can be recast so that they don't. The
problem is: it's a major pain in the ass.
Here's an example. Let's write a predicate which defines "sequences of
natural numbers" in a straightforward way.
sequence([]).
sequence([N|Ns]) :-
natural(N),
sequence(Ns).
natural([]).
natural(s(N)) :-
natural(N).
Now we write a predicate card(Ns,N), meaning that the cardinality (length)
of Ns is N:
card([], []).
card([_|Ns], s(N)) :-
card(Ns, N).
Now we look for sequences of cardinality 2 (s(s([]))):
sequence(Ns), card(Ns, s(s([]))).
We get one solution Ns=[[],[]], and on backtracking we fly off into the
weeds. I know that I could fold card/2 back into sequence/1, but I could
replace card/2 with an arbitrarily complex condition which might be very
difficult to unfold.
So here's the bottom line. In order to get REALLY robust predicates, it
boils down to the ability to do a robust generate-and-test:
interesting_object(X) :-
object(X),
interesting(X).
This should iterate over all interesting objects, where the interesting/1
predicate is arbitrarily complex. Of course, if there are no interesting
objects, then it will not terminate. But that's life, and I can live with
that.
Let's apply this criterion to our example. We must rewrite the domain
predicate sequence/1 to be a robust generator. To do this, we must adopt a
purely "constructive" representation for sequences:
sequence([]).
sequence([C|Cs]) :-
sequence(Cs),
construct(C, Cs).
construct([], _).
construct(s(C), [C|Cs]) :-
construct(C, Cs).
Now it's a little tricky to interpret these lists of constructors as
sequences of naturals, so I've provided a function interpret(Cs,Ns) which
maps a Cs-style sequence into the more familiar Ns-style sequence:
interpret(Cs, Ns) :-
sequence(Cs),
gather(Cs, Ns).
gather([], []).
gather([C|Cs], [C|Ns]) :-
toss(C, Cs, Cs0),
gather(Cs0, Ns).
toss([], L, L).
toss(s(N), [_|L], L0) :-
toss(N, L, L0).
If you invoke the query:
?- interpret(_, Ns), write(Ns), nl, fail.
then Prolog will begin enumerating all possible sequences of naturals, and
you'll see them in a familiar notation.
By the way, don't try to run the interpret/2 function backwards. You'll get
the unique solution, but on backtracking it'll go into the weeds. Oh well,
that's yet another nontermination quirk that I'll have to squash (if
possible).
Anyway, I've done these "constructive" representations for many kinds of
objects:
- natural numbers (trivial)
- rational numbers
- sequences of natural numbers
- partitioned sets
This will iterate over all ways of partitioning N objects into N
or fewer non-empty classes, for all N >= 0.
- directed acyclic graphs
This will iterate over all possible DAGs, with any number of
vertices and any number of edges between any two vertices. I
suspect that allowing cycles should be a fairly simple
extension.
- entire block design hierarchies with connectivity
A "design hierarchy" is used in computer-aided design.
As I said, this was a major pain in the ass. But O'Keefe is right: by
trying to make robust relations instead of just functions, I achieved some
strong insights.
The problem is: I'm not sure that my data structures are practical. In the
worst case, they're all O(N^2) in both time and space. I could get rid of
the s/1 notation for numbers and save some space, but that's about all I can
do in Prolog. However, it's interesting that all of these structures could
be implemented with arrays and integer indices in a language like C, and
they'd be really fast! (But I'm not in a great hurry to code in C.)
>> In Prolog, there is a wide divergence between theory and practice
> The width of the difference is a good measure of the ability of the
> programmer. Wide gap = bad programmer or bad implementation.
The problem is that I held onto the theory like a man possessed, and I might
have ended up with something totally impractical. Perhaps I'm a bad
programmer. In order to make things more efficient, I'll have to start
using more ad-hoc data structures. For example, I could represent "sets" as
trees and get O(logN) access time. But I'd have to give up my so-called
"constructive" ideal: I doubt that it's possible to write omni-directional
predicates for manipulating trees, since they're doubly-recursive.
>> That allows me to do things
>> like manipulate graphs as full-fledged objects, rather than as data
>> scattered around in extensional relations. I can do that in pure Prolog,
>> too, but it ain't easy.
> Damn right it's easy. I've done it.
Yeah, but I'll bet your graphs weren't "constructive". I'll bet you used
atoms as node identifiers, didn't you? (:-)
> The theory is not "pure Horn clauses" but "first-order logic".
That's where we differ. Notice that all my code consists of pure Horn
clauses. I understand that I have to give up that ideal in order to do I/O
and machine arithmetic, but I stick to the ideal as long as possible.
Besides, what is "first-order logic" other than Horn clauses? (That's not a
rhetorical question.)
> In NU Prolog, there are logical versions of not/1 (not/1 *is* the logical
> version, as in DEC-10 Prolog and Quintus Prolog; \+ /1 is the unsound one)
> and all-solutions predicates, and thanks to indexing, a competent programmer
> doesn't need many cuts at all. And while NU Prolog is not the world's
> fastest Prolog system, it's not bad.
I'd like to see NU Prolog working on this database:
person(pat).
person(diana).
with this query:
?- not person(X).
I'm guessing that either X remains an unbound variable which is constrained
to not be a person, or the query freezes on X. What DOES happen?
As far as "cuts" go, you certainly don't need them in functional languages:
they are replaced with "commitment". Of course, you still have order
dependencies, where a clause is chosen only if its guard is true and the
disjunction of the preceding guards is false. But "commitment" is
fundamentally syntactic sugar, whereas "cuts" are not. You could replace
"cuts" with negated conjunctions, but how do you define negation? In terms
of cuts, most likely.
>> Furthermore, functional programming languages are strongly typed and
>> higher-order, which cannot be said for Prolog.
> Functional languages do not have to be strongly typed.
> Prolog *can* be strongly typed. My formal specification of Prolog is
> a strongly typed Prolog program. (NU Prolog these days has two type
> checkers that I know of. Lee Naish's one is really amazing.)
> Prolog-as-we-know-it is not higher order, but that doesn't stop me using
> maplist and such things in Prolog, and there is Lambda Prolog which is a
> higher-order logic-based language.
As I read about functional programming languages such as Miranda or ML, I am
impressed with their purity, coherence, simple model of execution, and
support for large-scale and higher-order programming. Every program
corresponds to a lambda expression, and execution is essentially
beta-reduction. Modules, concrete types, abstract types, and functional
data are a coherent part of the language.
Now pure Prolog is a slick little language, too. But then you add cuts,
negation, and findall. And then you add type checkers and mode
declarations. And then you invent extended languages. You stick all these
"bags" on the side of Prolog, and the result just doesn't cohere as well as
I'd like.
Let's say I want to write a Prolog program which maintains a dynamic
database of facts. I can store the facts in the internal database and use
assert and retract. But that's not clean. So I decide to represent the
database as a data structure and filter a transaction stream through it. I
write "relations" which edit the data structure, but they're not reversible,
and in general it is IMPOSSIBLE to make them so. (Note well: impossible.)
Certain query modes will be meaningless and probably nonterminating.
Since in general I cannot implement completely robust relations, then give
me a language that allows me to document functional dependencies and types,
and make them meaningful to the compiler. That would be more "honest" than
a language which LOOKS relational but really isn't, and is fraught with
dangers if you think that it is.
> One thing which Prolog programmers ought to copy from functional programming
> is the determination to stick with the theory as long as possible: it
> really is the case that clean Prolog code tends to be faster than hacky
> stuff.
I think that I might have stuck with the theory longer than possible (:-).
In my original posting, I mentioned that some functional languages have
destructive assignment. In response:
>markv@phoenix.Princeton.EDU (Mark T Vandewettering) writes:
> AIIIGH! The horror! ASSIGNMENT?
>
> Destructive assigment is an atrocity, and totally against the spirit of
> functional programming, neatly messing up lots of nice properties. I am
> for a language without the crutch of assignment.
I agree. I am for a pure language without crutches. What I failed to
emphasize was that with one exception, EVERY example in "Elements of
Functional Programming" was written in the PURE language. Destructive
assignment was used in only one place, simply to demonstrate how lazy
evaluation could be implemented in an eager language. In contrast, MOST
Prolog programs use "impure" constructs and are not purely "relational". So
are we in the position that virtually EVERY functional program is purely
functional, but virtually NO Prolog programs are purely relational?
Although I may sound extremely hostile to Prolog, keep in mind that I have
learned things using Prolog that I probably wouldn't have learned using
Miranda. And I'm not "sold" on functional programming: it seems less
general than logic programming. It is certainly the case that the class of
mathematical relations is a proper superset of the class of mathematical
functions. But pure relations are so difficult to implement robustly, and
pure functions are so easy, that I wonder what is going on here. Perhaps
relations are more general, but functions are more computationally "honest".
Or what?
Patrick Chkoreff 719-590-5983
{hpfcla,hplabs}!hpldola!patch
Technical Details
Message-ID:
<11500019@hpldola.HP.COM>Path:
arc1-ca.pugleaf.net!utzoo!utgpu!jarvis.csri.toronto.edu!rutgers!ucsd!helios.ee.lbl.gov!pasteur!ucbvax!hplabs!hpfcso!hpldola!patchReferences:
<11500018@hpldola.HP.COM>Show Full Headers
Complete Article Headers
Path: arc1-ca.pugleaf.net!utzoo!utgpu!jarvis.csri.toronto.edu!rutgers!ucsd!helios.ee.lbl.gov!pasteur!ucbvax!hplabs!hpfcso!hpldola!patch From: patch@hpldola.HP.COM (Pat Chkoreff) Newsgroups: comp.lang.prolog Subject: Re: Fun. vs. Logic Message-ID: <11500019@hpldola.HP.COM> Date: 22 Nov 89 07:27:54 GMT References: <11500018@hpldola.HP.COM> Organization: HP Elec. Design Div. -ColoSpgs Lines: 330