If you ask a number of computing science graduates (especially from the 90's) a few will have used the
Prolog language at some point. Prolog is a logic language - think of it as defining a lot of facts and matching the fact with some data (like doing a database lookup). You run a "query" by matching your rules to your facts.
In my final year of my degree I worked on a project to create a "Graphical Interface for Prolog". The project represented (I think) 25% of the final score for the year and this project made me a developer. The idea for the project came from a poll of possible projects - I was told at the end that they thought the work that would be done in this project would be mainly theoretical. However, I ended up producing a "product" - in fact two products. (I also ended up winning the IBM prize that year for the best project).
We already had a Prolog interpreter for the classes we did in Artificial Intelligence - and the source code for it, which was written in C by
Fernando Pereira whilst he was doing a PhD at Edinburgh University. I took his code and added some hooks into it to allow it to be extended. This gave a mechanism to add new methods working with the Prolog data types. I then wrote a set of graphical predicates (essentially functions that said whether they worked or not - can could pass back data).
For a few years I had been meaning to try and get this up and running again. I did have the source code - but on 5.25" floppy disks (which I can't read any more). But I did have the printouts.
I looked around on the internet for a Prolog interpreter and found
Prolog.NET for which there was source code (written in C#). So, it being the holidays I decided on doing a bit of sofa coding - although after four or five hours, I do wish I had just sat at a desk with multiple monitors and to save all the papers thrown about the floor!
So, here are a few instructions if you want to do your own extensions. The main piece of code to add to is
Library.cs which has a list of predicates and functions. I added the following code.
standard.Add("graphics", 1, new PredicateDelegate(Graphics.graphics), false);
standard.Add("axes", 2, new PredicateDelegate(Graphics.axes2), false);
standard.Add("grset", 2, new PredicateDelegate(Graphics.grset), false);
standard.Add("point", 1, new PredicateDelegate(Graphics.point), false);
standard.Add("line", 2, new PredicateDelegate(Graphics.line), false);
standard.Add("rectangle", 2, new PredicateDelegate(Graphics.rectangle), false);
standard.Add("wait", 1, new PredicateDelegate(Graphics.wait), false);
These predicates that I am defining (
graphics, axes, grset, point, line, rectangle) are the same ones I did twenty years ago - just need to do an implementation. My implementation will be with Windows Forms - but without events. This makes it difficult to get mouse button presses, so I have another predicate (
wait) which will wait for a period of time.
Other examples of code already written is in the folder
LibraryMethods. I added a file
Graphics.cs and changed the namespace and the class definition as below
namespace Prolog
{
internal static class Graphics
{
// Your code
}
}
I initially started "hacking" the code trying to keep track of an implementation of a Windows Form instance within my Graphics class. But stopped. The class was now doing more than one thing - dealing with the Prolog predicates as well as working out the Windows Forms. And was getting confusing.
Looking at my old project in C I had a set of methods called
s1wg - Simple One Window Graphics. The implementation behind this was with X-Windows. Guess what - I decided to use the same "interface" - although now within a class. It was actually good to see that the implementation I did all these years ago (before getting real experience) was essentially implementing the S of
SOLID (Single Responsibility Principle). I could write this code separately (in another project) and check it was working without worrying about the Prolog part (and that was giving me some problems as well). So
s1wg is a simple class with methods to
- Constructor
- Close down (close the window)
- Create a window (with specified position, size and title)
- Draw a point
- Draw a line (between two positions)
- Draw a rectangle (with a position, width and height)
I then went back to work with the Prolog parts. The predicates we have are
- graphics - with a parameter to initialise or close (e.g. graphics(initialise) or graphics(close)).
- axes - to define a virtual axes (e.g. axes([-1,-8],[8,2])).
- grset - to set something (e.g. grset(size,[400,400]) sets the window size in pixels)
- point - to draw a point using virtual co-ordinates (e.g. point([3,3])).
- line - to draw a line between virtual co-ordinates (e.g. line([2,5],[5,6])).
- rectangle - to draw a rectangle a particular position with specified width and height (e.g. rectangle([1,3],[5,6])).
- wait - taking a parameter for a delay in milliseconds (e.g. wait(100)).
I'll get the code put up somewhere in case anyone wants to try this. Here is however an example of some of the code
public static bool line(WamMachine machine, WamReferenceTarget[] arguments)
{
Debug.Assert(arguments.Length == 2);
WamCompoundTerm lowerLeft = arguments[0].Dereference() as WamCompoundTerm;
WamCompoundTerm upperRight = arguments[1].Dereference() as WamCompoundTerm;
if (lowerLeft == null || upperRight == null)
return false;
if (lowerLeft.Functor == Functor.ListFunctor &&
upperRight.Functor == Functor.ListFunctor)
{
if (lowerLeft.Children.Length != 2 || upperRight.Children.Length != 2)
return false;
double? llX = GetDouble(lowerLeft.Children[0]);
WamCompoundTerm t = lowerLeft.Children[1].Dereference() as WamCompoundTerm;
double? llY = GetDouble(t.Children[0]);
double? urX = GetDouble(upperRight.Children[0]);
t = upperRight.Children[1].Dereference() as WamCompoundTerm;
double? urY = GetDouble(t.Children[0]);
if (llX == null || llY == null || urX == null || urY == null)
return false;
s1wg.DrawLine(ScaleX((double)llX), ScaleY((double)llY),
ScaleX((double)urX), ScaleY((double)urY));
}
return true;
}
This piece of code is checking for two parameters - which are Prolog lists, e.g.
line([3,4],[7,8]). From this you need to extract the values (as
WamCompoundTerm) and then check that we have lists - then the lists both have two entries. I wrote a method to extract a double from each element. I had major problems with doubles, so ended up writing this bit of code
private static double? GetDouble(WamReferenceTarget argument)
{
WamValueDouble operandDouble = argument.Dereference() as WamValueDouble;
Double d = 0.0d;
if (operandDouble == null)
{
WamValueInteger operandInteger = argument.Dereference() as WamValueInteger;
if (operandInteger == null)
{
WamValueString operandString = argument.Dereference() as WamValueString;
if (operandString != null)
{
if (Double.TryParse(operandString.Value, out d))
{
return d;
}
else
return null;
}
else
return null;
}
else
return (double)operandInteger.Value;
}
else
return operandDouble.Value;
}
Ok - definitely needs cleaning up. I check if I can get a double (which I cannot ever get working), then check if we have an integer - if it isn't an integer I check for a string that can be converted into a double. Note I am using the nullable double functionality. I am pretty sure I told my apprentices that if you start using structures like this it is probably a "code smell" of a bad design!
There are some other methods (
ScaleX and
ScaleY) that translate between the virtual a real co-ordinates.
What was the point of this graphical interface. It allowed you to see problems solved graphically. My first one isn't really a logic problem, but shows the use of look-ups. This exercise is "chain encoded images" - for a chain of numbers I draw lines in the direction depending on each number.
Firstly, lets have some "facts"
vector(["0"],[1,0]).
vector(["1"],[1,1]).
vector(["2"],[0,-1]).
vector(["3"],[-1,1]).
vector(["4"],[-1,0]).
vector(["5"],[-1,-1]).
vector(["6"],[0,1]).
vector(["7"],[1,-1]).
These are a list of vectors - if I specify "1" I will move 1 unit on the X axis and 1 unit on the Y axis. So if you execute the command
:-vector(["0"],C).
You will get output that
C is [1,0]. Next I will have a predicate
newposition which will calculate a new position
newposition([X1,Y1],[DX,DY],[X2,Y2]) :-
(X2 is (X1 + DX)),
(Y2 is (Y1 + DY)).
Running the following command
:-newposition([3,4],[1,2],NewPosition).
Then you will get the
NewPosition as [4,6]. Hooking this up to
vector you can use this like
:-vector(["2"],Delta),
newposition([4,2],Delta,NewPosition).
will give the output
Delta = [0,-1]
NewPosition = [4,1]
One of my main issues with this implementation was getting lists working. Lists in Prolog are managed a the first element in the list - and the remaining items. The code to draw the chain consists of two predicates. Prolog can/is a very recursive language. The first version we have of this predicate does the main work
drawchain([H|T],S,P) :-
vector([H],D),
newposition(P,D,N),
line(P,N),
wait(100),
drawchain(T,S,N).
Here the
drawchain predicate takes a list as the first parameter
[H|T] where
H is the head of the list (the first element) and
T is the tail (the remaining elements). The remaining parameters are
S (for start position) and
P (for current position). Take each line in turn
- vector([H],D) uses the head element to get the value D (the delta/change from position)
- newposition(P,D,N) calculates the new position N using the current position P and delta D
- line(P,N) draws a line between P and N
- wait(100) will wait 100 millseconds
- drawchain(T,S,N) will call the drawchain predicate with parameters S (the start position) and N (the new position we just calculated) with T being the tail of the list.
The next predicate is the stop condition - when the list is empty. In this case we want the predicate to draw a line from the current position to the start position (the "parameters"
P and
S respectively).
drawchain([],S,P) :-
line(P,S).
Now to execute this I have the predicate below
chains :-
Chain is ["0","1","6","3","4","5","2"],
axes([-5,-2],[11,6]),
graphics(initialise),
drawchain(Chain,[0,0],[0,0]).
Which is executed with
:-chains.
And you get this.
|
Chain Encoded Images |
It's been really quite fun to play with this. Not sure if I have wasted a day of my time, but interesting to see the work I did a few years ago in operation. I have other examples to get up and running with this (Towers of Hanoi, Binary Trees and Travelling Salesman) which I implemented with this interface. As well as completing the graphics predicates (text, filled shapes etc.)
The good news is that I did in less than a day work that I did over a three month period in 1990 (albeit with a little bit of an idea now of what I am doing).