AI-Prolog

 view release on metacpan or  search on metacpan

lib/AI/Prolog/Article.pod  view on Meta::CPAN

now, you know that the following can be read as "Ovid loves Perl":

 loves(ovid, perl).

Assuming you've consulted a file with that fact in it, querying to find out
what I love is simple:

 ?- loves(ovid, WHAT).

In Perl, it's also pretty simple:

 %loves = ( ovid => 'perl' );
 $what  = $loves{ovid};

But how do we find out who loves Perl?  In Prolog:

 loves(WHO, perl).

In Perl, however, we have two options, neither of them particularly good.  We
can scan the C<%loves> hash for entries whose value is Perl, but this is
C<O(n)> when we want to stick with our simple C<O(1)> check.  Thus, a more
common solution is to reverse the hash:

 %who_loves = reverse %loves;
 $who       = $who_loves{perl};

(C<AI::Prolog>, being written in Perl, is slow.  So the C<O(n)> versus
C<O(1)> argument doesn't hold.  Versions written in C are far more practical in
this regard.)

But this fails, too.  The first problem is that we have duplicated data.  If we
have to add additional data to the C<%loves> hash, we'll have to remember to
synchronize the hashes.  The second problem is that Perl is just a little too
popular:

 loves(ovid,    perl).
 loves(alice,   perl).
 loves(bob,     perl).
 loves(charlie, perl).

If we simply reverse the hash for those entries, we lose three of those names.
So we have to play with this some more.

 while ( my ($person, $thing) = each %loves ) {
     push @{ $who_loves{$thing} }, $person;
 }

Oh, wait.  This fails too.  You see, I'm fickle.

 loves(ovid, perl).
 loves(ovid, prolog).
 loves(ovid, 'Perl 6').

(The quotes around "Perl 6" tell Prolog that this is a single value and that
it's a constant, not a variable.)

How do I find out everything Ovid loves?  In Prolog, the query doesn't change:

 loves(ovid, WHAT).

In Perl, our original hash wasn't enough.

 my %loves = (
     ovid    => [ qw/perl prolog Perl6/ ],
     alice   => [ 'perl' ],
     bob     => [ 'perl' ],
     charlie => [ 'perl' ],
 );

 my %who_loves;
 while ( my ($person, $things) = each %loves ) {
     foreach my $thing ( @$things ) {
         push @{ $who_loves{$thing} }, $person;
     }
 }

Now that's starting to get really ugly.  To represent and search that data in
Prolog is trivial.  Now do you really want to have fun?

 gives(tom, book, sally).

How would you represent that in Perl?  There could be multiple gift-givers,
each gift-giver could give multiple things.  There can be multiple recipients.
Representing this cleanly in Perl would not be easy.  In fact, when handling
relational data, Perl has several weaknesses in relation to Prolog.

=over 4

=item * Data must often be duplicated to handle bi-directional relations.

=item * You must remember to synchronize data structures if the data changes.

=item * Often you need to change your code if your relations change.

=item * The code can get complex, leading to more bugs.

=back

=head2 Prolog versus SQL

At this point, there's a good chance that you're thinking that you would just
stuff this into a relational database. Of course, this assumes you need
relational data and want to go to the trouble of setting up a database and
querying it from Perl.  This is a good solution if you only need simple
relations.  In fact, Prolog is often used with relational databases as the two
are closely related.  SQL is a I<special purpose> declarative language whereas
Prolog is a I<general purpose> declarative language.

Firing up SQLite, let's create two tables and insert data into them.

 sqlite> CREATE TABLE parent_2 (parent VARCHAR(32), child VARCHAR(32));
 sqlite> CREATE TABLE male_1   (person VARCHAR(32));

 sqlite> INSERT INTO parent_2 VALUES ('sally', 'tom');
 sqlite> INSERT INTO parent_2 VALUES ('bill', 'tom');
 sqlite> INSERT INTO parent_2 VALUES ('tom', 'sue');
 sqlite> INSERT INTO parent_2 VALUES ('alice', 'sue');
 sqlite> INSERT INTO parent_2 VALUES ('sarah', 'tim');

 sqlite> INSERT INTO male_1   VALUES ('bill');
 sqlite> INSERT INTO male_1   VALUES ('tom');



( run in 0.774 second using v1.01-cache-2.11-cpan-cdf2f3d4e48 )