Python v. Prolog: Round 1: Fight!

Finally, I’ve made some early progress in implementing Old Lady’s bidding system logic in Prolog. Specifically, SWI-Prolog.

While GNU Prolog looks like it has all the features Old Lady would need, it doesn’t look like it’s quite as robust a Prolog implementation as SWI-Prolog is. For example, GNU Prolog doesn’t have a module system, which might be useful whenever Old Lady supports more than just one bidding system — for example, you might want generic stuff in one module, and each particular bidding system in its own module. Also, integrating GNU Prolog into a Python program looks like a trickier proposition than I initially thought. Although GNU Prolog has a Prolog-to-C interface, it’s geared towards embedding a static copy of GNU Prolog into a standalone C program. Getting its compiler wrapper to output a shared library suitable for wrapping a Python module around is probably doable, but would involve more trickery than I’d really care to have to implement.

Which is all not to say that the road to using SWI-Prolog has been particularly wonderful, either. The Debian packages for it are pretty out of date (the last two uploads were in June 2006 and July 2004!). PySWIP provides a Python interface to SWI-Prolog, but requires SWI-Prolog’s shared library; there’s a five-year-old wishlist bug for the Debian packages to include that library, which they currently don’t. While supposedly there is work on getting a non-ancient version of SWI-Prolog packaged, you’ll forgive me if I don’t hold my breath.

Fortunately, compiling the latest SWI-Prolog from source and installing it manually wasn’t too difficult. Unfortunatley, the last released version of PySWIP (0.2.1) doesn’t actually work, so I had to grab the latest code from the SVN repository, which at least is functional.


My intention is to issue a Prolog query of the form

Hand=[...], History=[...], choose_bid(Bid, Hand, History)

where Hand is a list of cards in the player’s hand and History is the list of bids that have been made previously by all players. What the Prolog code will do is find a value for Bid that looks something like bid(1,hearts) or bid(pass), or something along those lines. Nothing fancy.

Unfortunately, PySWIP (or at least the version in the SVN repository) doesn’t provide very useful information out of a query like this. Say for example I issue the following query through PySWIP (with extra whitespace added between the main terms, for readability’s sake):

Hand=[card(7,clubs), card(8,clubs), card(9,clubs), card(ace,clubs), card(6,diamonds), card(8,diamonds), card(ace,diamonds), card(2,hearts), card(10,hearts), card(queen,hearts), card(king,hearts), card(5,spades), card(8,spades)],
choose_bid(Bid, Hand, Hist)

For each solution found, PySWIP returns a dict with what term is assigned to each variable. In the above, PySWIP “helpfully” returns the string 'Functor5075212' as the value assigned to Bid.

Yes, PySWIP, thanks for telling me it’s some complex term. And giving me no way to look at what the term actually is.

I can only get a useful result back if I only ask for simple atoms to be assigned to the variables in the query. But since I don’t know whether there are one or two parameters to the bid term, I have to effectively do two separate queries, one for each case:

Hand=[card(7,clubs), card(8,clubs), card(9,clubs), card(ace,clubs), card(6,diamonds), card(8,diamonds), card(ace,diamonds), card(2,hearts), card(10,hearts), card(queen,hearts), card(king,hearts), card(5,spades), card(8,spades)],
( choose_bid(bid(Level, Denom), Hand, Hist) ; choose_bid(bid(Action), Hand, Hist) )

That is, use a logical disjunction between two separate calls to the choose_bid predicate, which ends up doing twice the work as the original, “correct” query. But at least PySWIP returns useful information in this case:

'Denom': 'clubs', 'Level': 1, 'Action': Variable(362)

I can only assume this sort of brokenness in PySWIP is a bug, and not actually what it’s intended to do for non-atomic terms assigned to variables. (It also gives me the assignments to Hand and Hist, which are also expressed using the nonsense Functor notation, but at least for those it gives me the paramter list too, which would actually be useful, if not for the fact that I already told PySWIP what those variables’ values are, and in fact that I only made them variables instead of inlining them directly in the call to choose_bid since it’s forcing me to make two calls to choose_bid in the first place!)

Suboptimal, yes, but it is at least usable, and the ugliness can be safely hidden in my Python class that builds these queries and parses out the results.

For the truly adventurous, the code is in the Old Lady bzr repository, though it’s not really playable yet, since breathtakingly little of the Standard American Yellow Card has yet been implemented.

5 Responses

  1. Instead of:

    stat_length(Stats, spades, Length) :- Stats = stats(_, Length, _, _, _).

    you can just write:

    stat_length(stats(_,Length,_,_,_), spades, Length).

    (Similarly for the other cases.) Also, since SWI Prolog uses the topmost functor of the first argument (if available) to index predicates, consider flipping the first and second argument to make use of first argument indexing:

    stat_length_(spades, stats(_,Length,_,_,_), Length).

    Also, why is StatList a list? There are some rules of thumb for when to use lists: Does the empty list make sense? Does it make sense to remove/add one element? etc. In this case a list doesn’t seem to make sense, and it’s cheaper and clearer to use a term like stats/4.

  2. Thus far I’ve given very little thought to the efficiency of the Prolog code, so there’s no real reason I’m doing some things in a sub-optimal way.

  3. Have you considered using an elseiffor scheme?

  4. have you considered an audacious plugin for the music-applet?

  5. Benji: Heretofore, no.

    sr: Not yet.

Comments are closed.