Skip to content

aychtang/prolog-beginner-guide

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

32 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Guide to Prolog from the beginner:

Table of Contents

  1. Intro
  2. Facts
    1. Querying Facts
    2. Substitution
  3. Rules
    1. Combining Fact checks
    2. Comparing Fact checks with different arguments
    3. Composing Fact checks
    4. Finding all solutions to a rule
  4. Using SWI-Prolog
    1. Loading a file into Prolog environment
    2. Halting the Prolog environment
    3. Reloading the Prolog environment
  5. Lists
    1. List declaration
    2. Working with Lists
    3. Recursion with Lists
  6. Practical Prolog
    1. Main function
    2. TDD in Prolog

Intro

What is Prolog anyway? It is a logic based programming language where you define a set of facts that live inside the program, and can build rules that operate upon those facts to work toward a goal.

Facts:

The syntax for defining a Term, is the following:

factname(relation1, relation2 ... relationN) :- Body (also called goal).

Let's add a few Facts to our programs universe:

male(zeus).
female(mnemosyne).
female(clio).

parent(zeus, clio).
parent(mnemosyne, clio).

This block of code then is defining a few Facts within the universe of the program:

There is a male Zeus.
There is a female Mnemosyne.
There is a female Clio.

Zeus is the parent of Clio.
Mnemosyne is the parent of Clio.

Note that since we are defining Facts, none of them have a Body (or goal), but simply resolve to true. A Fact is a Term which has no body.

male(zeus).
% is synonymous with
male(zeus) :- true.

Querying facts

When running this code with a Prolog interpreter we can then run some queries on this set of facts and look at some cool features of Prolog already:

You can execute a Fact with arguments to see if the relation exists in the universe:

male(zeus). % true.
female(mnemosyne). % true.
female(zeus). % false.

parent(zeus, clio). % true.
parent(zeus, mnemosyne). % false.

Substitution

You can also leave out an argument of a Fact, to see if there are any valid substitutions:

male(X). % X = zeus.
female(X). % x = mnemosyne.

parent(X, clio). % X = zeus.

Where does the X come from?

In Prolog you can add free (or real) variables into your expressions to indicate that a substitution should be made. In the same way you do in an algebraic expression (e.g: 5 = X - 3), when you add a variable that is not in scope of your Prolog expression it will attempt to find a solution for those variables. Here is an example where we add two free variables into a fact check:

parent(X, Y).

% X = zeus,
% Y = clio.

Iterating through solutions

If there can be more than one solution to the variable within a term, you can iterate through them in the Prolog environment by pressing ;, the environment will continue listing solutions until none are left.

parent(X, clio).
% X = zeus ;
% X = mnemosyne.

Rules

You can write rules which use or combine Facts together to work toward a goal.

Combining Fact checks

Let's say we want to find who the mother of a certain person is within our universe, and we wanted to do that through the interface mother(parent, child). We could of course simply add a new Fact into the universe, which declares the mother relationships that exist, however we can reduce the number of Facts we have to declare by writing Facts that can be possibly computed from existing relationships into these rules.

We can logically define a mother to be a parent of a child, who is also the female parent.

Expressed as a Prolog rule that would look something like this:

mother(P, C) :-
    parent(P, C),
    female(P).

mother(X, clio). % X = mnemosyne.

Given an argument P and C, where the Fact holds parent(P, C) and the Fact holds female(P). We can define that P to be the mother of C.

Comparing Fact checks with different arguments

Clio was also given many sisters, so let's add one of them into our universe:

male(zeus).
female(mnemosyne).
female(clio).
female(euterpe).

parent(zeus, clio).
parent(mnemosyne, clio).
parent(zeus, euterpe).
parent(mnemosyne, euterpe).

How can we define a rule to check if Clio is a sibling of Euterpe?

We could say if they each have the same mother, then they are siblings. Expressed in Prolog this would look something like:

is_sibling(C1, C2) :-
    mother(X, C1),
    mother(X, C2).

is_sibling(clio, euterpe). % true.

Given arguments X, C1, and C2 if mother(X, _) holds true for both C1 and C2, we can define C1 and C2 to be siblings.

Composing Fact checks

Something missing from the is_sibling code above, would be siblings that share a father but not the mother, so we would have to make an adjustment to write a better version of the rule.

father(P, C) :-
    parent(P, C),
    male(P).

is_sibling(C1, C2) :-
    mother(X, C1), mother(X, C2)
    ;
    father(X, C1), father(X, C2).

Here we add the father(P, C) rule which was not yet defined, and also use it in is_sibling. The ; symbol denotes a logical OR, so either the expression before it or after it has to hold true.

Finding all solutions to a rule

A useful query to make would be to find all possible solutions to a substitution and return it as a list to be used by a later portion of the program, for example what if I wanted to find all the parents of a person within our universe? To do that we can use the builtin forall/3.

findall/3 takes three arguments findall(Object, Goal, List).

findall(X, parent(X, clio), PS). % PS = [ zeus, mnemonsyne ]

By extension if we wanted to find all children of a person, we would be able to use findall with the arguments of parent swapped around.

findall(X, parent(zeus, X), CS). % CS = [ clio, euterpe ]

Using SWI-Prolog

The standard usage for swipl will be to load a file, it will then allow you to execute commands in a Prolog REPL environment. I've left a full program of the family tree code in the file godfamily.pl. Here is how you can load the file using swipl, execute a query, and then close the REPL.

Loading a file into Prolog environment

$ swipl -l godfamily.pl

Welcome to SWI-Prolog (threaded, 64 bits, version 8.0.2)
SWI-Prolog comes with ABSOLUTELY NO WARRANTY. This is free software.
Please run ?- license. for legal details.
For online help and background, visit http://www.swi-prolog.org
For built-in help, use ?- help(Topic). or ?- apropos(Word).

?- findall(X, parent(zeus, X), CS).
CS = [clio, euterpe, thalia, melpomeni, terpsichore, erato, polymnia, ourania, calliope].

?- halt.

$

Halting the Prolog environment

The best way to exit the Prolog REPL is to run the halt. command.

Reloading the Prolog environment

If you want to reload the environment with changes to the file you've made since you opened it, you can run the make. command.

Lists

The returned value assigned to CS from the the command we executed in swipl is a list. We can tell it's a list because Prolog says so:

is_list([clio, euterpe, thalia, melpomeni, terpsichore, erato, polymnia, ourania, calliope]) % true

List declaration

Lists can be declared as empty, or as a comma separated list of Atoms, this looks like you'd expect in most programming languages.

[]
[a, b]
[1, 2, 3]

Working with Lists

There is a builtin append/3 which has the parameters (InitialList, Insertee, NewList).

append([1], 2, XS).
XS = [1|2].

append([1], [2, 3], XS).
XS = [1, 2, 3].

Notice how in the first evaluation where there is a List returned with only two elements inside, it is shown as [1 | 2], this is because Lists can be represented and unified as the head and tail of the list. Lists can be represented [head|tail] where head will be the first element of the list, and tail will be the remaining elements of the list.

The second XS value could be rewritten as [1|[2,3]].

Recursion with Lists

In order to recurse a list in Prolog you must first define the base case to ensure it terminates, and then you can write the recusive logic in another rule definition that has head or tail values.

% First define the base case:
count([], A, A).

% Then write the recursive function:
count([H | T], A, C) :-
    A1 is A + 1,
    count(T, A1, C).

count([1, 2, 3], S) % S = 3.

Another demonstration of recursion with lists, would be to find whether an element is a member of a list:

member([], E) :-
    false.
member([H, T], E) :-
    H = E
    ;
    member(T, E).

Here we define the base case where member is called with an empty array to be false. Then in the member function which has elements in the list, either H = E or member(T, E) must be satisifed.

Practical Prolog

Main function

A pattern I've found quite useful when writing Prolog programs is to use a main function that is initialised on startup. In prolog you can define a command that is run on initialization using the :- intialization {command} syntax.

Here is how I would write a hello world program in Prolog:

:- intialization main.

main :-
    write('Hello world'), nl, halt.

TDD in Prolog

The motivation to learn Prolog initially was because I wanted to use it to solve a problem in the Tray.io code dojo. In our code dojo's we follow the practice of writing our solutions with a TDD approach which means I need to come up with some way I can define a set of test cases to test my Prolog program.

Prolog seems quite well disposed to writing a test harness for, since a lot of expressions will naturally end up being true or false. We can define an equals with two definitions, one where the arguments evaluate to the same value, and the other where they differ.

equals(T, A, A) :-
    format("OK: \"~w\" ~n", [T]).

equals(T, A, B) :-
    format("FAILED: \"~w\" - expected ~w and got ~w ~n", [T, A, B]).

equals("One equals one", 1, 1). % "OK: "One equals one""
equals("Three equals three", 3, 2). % "FAILED: "Three equals three" - expected 3 and got 2"

About

The Prolog guide written by the beginner.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages