My little Prolog adventure shed some light on why the language has had even less popularity than such relative box-office duds as LISP and Ada.
What I thought was cool about Prolog was that you could define a function in pieces. In one part of your program, you could have
valid_uri("http://" + authority + path + query_string) :- valid_authority_component(authority), valid_http_path(path);
(taking a little pedagogical license). In another part, you could have:
valid_uri("tag://" + authority + "," + date + ":" + opaque_portion) :- valid_authority_component(authority), iso_date(date);
Now you've got a function "valid_uri" which will tell you whether it's argument is a valid tag or http URI. I thought this was neat because if you don't know everything, you could just write what you know now, and later on, elsewhere, you or someone else could write what you or she knows then. This, I thought, would support a more improvisatory mode of programming.
The problem is that it's just not a good idea to code that way. When your function valid_uri starts misbehaving, you've got to go and hunt down all the places where it's partially defined, which could be anywhere. Likewise, if you need to change the interface, you've got to go hunt those down. Object-oriented programming offers more or less the same feature in a better way. Generally speaking, you can inspect an OOP object to see what type it is; and the method that gets invoked is determined by the type. OOP programmers almost universally keep their stuff together by putting all of a class's methods in one file (and in Java this is forced). That way you if you see that a function Pet::NeedsFeeding misbehaves for some objects, and if you determine that the faulty Pet objects are actually of type Cat, you know where to go to find the code for Cat::NeedsFeeding. This is good. Hunting through your code trying to imagine where it went wrong is bad (cf. GOTO).
So if you have any sense, you'll write your Prolog code with all of the partial definitions together, like this:
nth(n, []) :- throw Empty;
nth(0, H :: T) :- H;
nth(n, H :: T) :- nth(n-1, T);
Funny, but that's a lot like the way Standard ML's pattern matching works on terms:
fun nth(n, []) = raise Empty
| nth(0, H::T) = H
| nth(n, H::T) = nth(n-1, T);
Quite the same, eh? But with ML the cases of the pattern match are syntactically required to be together (foiling those runabouts who'd define the cases in different files!) and this pattern-matching construct can be pulled into a local scope without creating a top-level function (or any function at all). One thing this permits is easier debugging, if you need to print out the arguments at the beginning of the function before doing the pattern matching:
fun nth(n, Lyst) = (
print Int.toString(n) ^ List.toString(Lyst);
case (n, Lyst) of
(n, []) => raise Empty
| (0, H::T) => H
| (n, H::T) => nth(n-1, T)
);
I never thought I'd say that ML had a feature that's good for debugging, but when it's got a lead over Prolog, you gotta give props where props be due.
I had originally wanted to experiment with Prolog, and with so-called unification, because I wanted to screw around with the OpenMind CommonSense database, and see what kinds of connections it could make. But doing searching/unification on a database of natural language terms is somewhat different from doing it in a programming language context. The core searching/unification algorithm is probably about the same (haven't tried it yet!) but I bet there are lots of pragmatics that make none of the code transfer over. To say the least, you've got the database interface (much unlike my in-memory data structure), and you've got differring notions of what a variable is, and that's not getting into the issues around understanding a parsed natural language structure. that will have to be my next project. Anyway, just wanted to share my realizations about the Prolog approach to programming. I'm a bit ignorant of Prolog outside the theoretical realm, so I might be underestimating its usability; this is just from my experience with a rudimetary faux-prolog interpreter of my own design.
This is great thinking... But I can't help but wonder if our fantasy Prolog IDE of the year 2020 would be able to present virtual views of functions (assembled cross-source file) so you could use this cool feature and still be able to debug?
Ah! A reader! Grab it before it runs away!
It's a good point you make, Miranda, that a futurific IDE might pull all those definitions together. That would be pretty neat!
One thing comes to mind, though; sometimes you have foreign code invading your own at runtime, especially in an interpreted language. This comes up in my work, where I design interfaces that other developers can use to "plug in" to the core application. I went to elaborate ends to trap errors in plugins, so that people can't just complain, "I tried to save my thing and it just died." Or if they do complain, I'll know that it's not some vagabond code wandering in off the streets.
With Prolog, even with such a nifty IDE, wouldn't you expect code in the field to come back worn, bearing the dents and abrasions inflicted by strangers?
You might take a look at ConceptNet (www.conceptnet.org), which is a somewhat more structured variant of the Open Mind data. It's a semantic network with 20 link types, but the nodes are simple NL phrases. I've read it into Prolog and in a sense it works fine (you can look up the predicates quickly), but if you want to write inference rules you might want (a) to find a way to match similar phrases and (b) to develop some rules of inference that relate the various 20 predicates (e.g. transitivity of containment for locationOf, or things like doneAt(action, place) :- usedFor(object, action) ^ locationOf(object, place)). You may need to find a way to incorporate confidences into your inferencing, since the Open Mind data is noisy (but in my opinion, noise is unavoidable in any sufficiently large KB.)
Push:
Thanks for the tip. I had actually discovered ConceptNet a couple days ago and had already drafted a post about it, but wasn't finished.
I'm impressed with the overall signal:noise ratio in the ConceptNet corpus, but certainly there's enough noise to foul up the works. For one thing, it seems to conflate different senses of a word. I know the OpenMind website had done a project to use website visitors (= monkeys) to accumulate data for disambiguation. Did that database ever get released?
Sometimes ambiguity does cause trouble, although it can occasionally help (e.g. "chips are thin" applies to both potato chip and computer chips, although "chips taste good" don't).
Tim Chklovski is the person to contact about the disambiguated OMCS data. We've looked at using automatic context-based disambiguation methods, but haven't made a serious effort to disambiguate ConceptNet yet. What I'm hoping is that if we can disambiguate just a subset of the nodes, there will be some way to more automatically constrain the meanings of connected nodes.
