Wprowadzenie
Obok gigantycznego świata języków imperatywnych typu C, C++, C#, Java, etc. istnieje cała gałąź języków deklaratywnych. Chyba najpopularniejszym z nich jest Prolog (od ‘Programowanie w Logice’), powstały na początku lat 70. ubiegłego wieku i który do dzisiaj znalazł wiele implementacji i dialektów.
Zasadniczo programy napisane w Prologu różnią się tym od programów do których jesteśmy przyzwyczajeni, że opisują fakty i relacje między obiektami, a w mniejszym stopniu skupiają się na podawaniu kolejnych kroków algorytmu. Nie zamierzam jednak skupiać się na samym języku czy też jego zastosowaniach, a raczej na jednej z mniej znanych jego implementacji - P#.
Czym jest P#?
W dużym skrócie P# jest implementacją Prologu napisaną w C# zawierającą translator kodu Prologu do C#. Powstał w 2003 roku w ramach dysertacji doktorskiej Jonathana Cook’a i właściwie od tamtej pory (czyli wersji 1.1.3) nie rozwija się dalej. Przy tworzeniu P# Jon Cook opierał się na Prolog Cafe - narzędziu pełniącym tę samą funkcję, co P#, jednak językiem docelowym była Java i właściwie wszystkie konstrukcje dostępne w Prolog Cafe (wersja 0.6.1) przeniesione zostały do P#.
P# (Psharp) składa się z gigantycznej biblioteki Psharp.dll, zawierającej pełną implementację języka oraz wszystkich niezbędnych narzędzi do pracy w środowisku .NET, oraz dwóch programów interaktywnych do pracy z Prologiem:
1. PsharpGUI - graficzny edytor kodu wraz z interpreterem oraz kompilatorem kodu

2. PsharpIntrp - konsolowy interpreter.

Oba narzędzia są bardzo pomocne przy pracy z prologiem, jednak najważniejsze jest dostępne API, dzięki któremu możemy korzystać z dobrodziejstw Prologu w programach napisanych w C#.
OK. Tyle słowem wstępu, przejdźmy teraz do najważniejszego :)
Jak z tego korzystać?
Na ruszt weźmy przykładowy predykat ‘member’ ze świetnej książki W.F.Clocksina i C.S.Mellisha (polskie tłumaczenie!!!), weryfikujący, czy zadany obiekt jest elementem listy:
member(X,[X|_]).
member(X,[_|Ys]):-member(X,Ys).
No cóż, niby nic wielkiego, ale jaka siła drzemie w tych dwóch linijkach programu! Zauważmy, że nie określiliśmy, jakiego typu są elementy listy, więc równie dobrze możemy testować listy liczb całkowitych jak i listy napisów.
Uruchamiamy PsharpGUI.exe, wklejamy kod do dolnego okna i z menu ‘Editor’ wybieramy ‘Compile to C#’ (Ctrl+Shift+C). W katalogu aplikacji pojawił się plik Member_2.cs, zawierający kod Prologu zinterpretowany do odpowiadającemu mu w C#.
Mając przygotowany kod klasy C# możemy przejść do kolejnego kroku, czyli skorzystania z wygenerowanego kodu w naszym programie C#. Poniższy kod umieszczamy w pliku Program.cs i dołączamy do projektu plik Member_2.cs wygenerowany w poprzednim kroku.
using System;
using System.Collections.Generic;
using System.Text;
using JJC.Psharp.Lang; // 0
using JJC.Psharp.Predicates; //0
namespace pl.net.zine.Prolog
{
class Runner
{
static void Main(string[] args)
{
Runner p = new Runner();
p.test_Member();
}
public void test_Member()
{
PrologInterface sharp = new PrologInterface(); // 1
ListTerm intList = GetList(new int[] { 1, 6, 3, 8, 6, 7 }); //2
Term a2 = new IntegerTerm(9); // 3
Predicate member = new Member_2(a2, intList, new ReturnCs(sharp)); // 4
sharp.SetPredicate(member); // 5
bool b = sharp.Call(); // 6
Term result = a2.Dereference(); // 7
}
/// <summary>
/// Pomocnicza metoda do konwersji tablicy intów na ListTerm
/// </summary>
/// <param name="ints">tablica intów</param>
/// <returns></returns>
private ListTerm GetList(int[] ints)
{
Term empty = SymbolTerm.MakeSymbol("[]");
Term[] terms = new Term[ints.Length];
for (int i = 0; i < ints.Length; i++)
{
terms[i] = new IntegerTerm(ints[i]);
}
Term list = empty;
for (int i = terms.Length - 1; i >= 0 ; i--)
{
list = new ListTerm(terms[i], list);
}
return (ListTerm)list;
}
}
}
Kod całego programu nie jest długi, więc dla porządku i łatwego przetestowania podaję go w całości. Oto pokrótce opis tego co się w nim dzieje:
0. Ten krok właściwie nie wymaga komentarza - po dołożeniu referencji do Psharp.dll wprowadzamy odpowiednie przestrzenie do naszego kodu.
1. Przechodzimy do metody test_Member(), w której znajduje się cały interesujący nas kod. W pierwszym kroku inicjalizujemy interfejs Prologa.
2. Do utworzenia odpowiedniej listy przekazanej jako parametr predykatu Member potrzeba wykonać wiele kroków - stąd pomocnicza metoda GetList(int [] ints).
3. Tu tworzymy Term opakowujący liczbę całkowitą.
4. Tworzymy obiekt predykatu Member_2. Jak widać, nasz predykat ‘member’ zamieniony został w ‘Member_2’, czyli pierwsza litera nazwy stała się dużą literą (podobnie byłoby z pozostałymi członami oddzielonymi ‘_’: ‘my_member’->’MyMember’), natomiast ‘2’ pojawiająca się po ‘_’ określa ‘arność’ predykatu (w naszym przypadku jest to 2).
5. Bindujemy odpowiedni predykat do interfejsu Prologu.
6. Zadajemy przygotowane pytanie. W odpowiedzi otrzymujemy wynik typu bool, który informuje nas o tym, czy ‘9’ jest elementem listy, czy też nie. Oczywiście w wyniku dostajemy false.
7. W przypadku, gdyby zależało nam na wyliczeniu czegoś, to krok 3 powinien wyglądać następująco:
Term a2 = new VariableTerm();
a w kroku 7 dostajemy pierwszą odpowiedź - czyli w naszym przypadku będzie to ‘1’. W podanym przykładzie a2 zawiera oczywiście cyfrę ‘9’, ponieważ testowaliśmy jedynie, czy jednym z elementów listy nie jest cyfra ‘9’.
Drugi przykład będzie nieco bardziej rozbudowany - od strony kodu Prologu. Tym razem sięgniemy po kolejny przykład, tym razem z bodajże najlepszej książki poświęconej Prologowi - ‘The Art of Prolog’ autorstwa Leona Sterlinga i Ehuda Shapiro - realizujący algorytm quicksort.
select(X,[X|Xs],Xs).
select(X,[Y|Ys],[Y|Zs]):-select(X,Ys,Zs).
permutation(Xs,[Z|Zs]):-select(Z,Xs,Ys),permutation(Xs,Ys).
permuation([],[]).
ordered([]).
ordered([X]).
ordered([X,Y|Ys]):-X=<Y,ordered([Y|Ys]).
append([],Ys,Ys).
append([X|Xs],Ys,[X|Zs]):- append(Xs,Ys,Zs).
partition([X|Xs], Y, [X|Ls], Bs):- X =< Y, partition(Xs,Y,Ls,Bs).
partition([X|Xs], Y, Ls, [X|Bs]):- X > Y, partition(Xs,Y,Ls,Bs).
partition([], Y, [], []).
quicksort([],[]).
quicksort([X|Xs],Ys):-partition(Xs,X,L,B),quicksort(L,Ls),quicksort(B,Bs),append(Ls,[X|Bs],Ys).
Odpowiedni kod metody wywołującej algorytm quicksort w C#:
public void test_Sort()
{
PrologInterface sharp = new PrologInterface();
ListTerm intList = GetList(new int[] { 1, 6, 3, 8, 6, 7 });
Term a2 = new VariableTerm();
Predicate member = new Quicksort_2(intList, a2, new ReturnCs(sharp));
sharp.SetPredicate(member);
bool b = sharp.Call();
Term result = a2.Dereference();
}
Jak widać, z punktu widzenia kodu C# różnice nie są duże - Term a2 jest właśnie zmienną, w której chcemy mieć ‘na wyjściu’ posortowaną listę liczb.
Po wykonaniu tej metody term result zawiera tablicę [1, 3, 6, 6, 7, 8].
Zakończenie
Na koniec tego wstępu do P# jeszcze jeden programik.
Jak już wcześniej napisałem, w ramach pakietu dostajemy dwa programy wspierające interaktywną pracę z Prologiem i pozwalające na translację kodu Prologu do C#. Brakuje jednak narzędzia, które po prostu będzie ‘tłumaczyło’ kod prologu zawartego w pliku do odpowiedniego zestawu klas C#. Nic straconego! Okazuje się bowiem (co w gruncie rzeczy nie powinno dziwić), że w ramach biblioteki Psharp jest predefiniowany predykat realizujący ‘compile(‘nazwa_pliku’)’ i nazywa się oczywiście ‘Compile_1’ [:)]. Teraz, gdy już wiemy w jaki sposób ‘wywoływać’ predykaty, poniższy kod możemy pozostawić bez komentarza.
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using JJC.Psharp.Lang;
using JJC.Psharp.Predicates;
namespace Prolog2CSharp
{
class Program
{
static void Main(string[] args)
{
if (args.Length != 1 || !File.Exists(args[0]))
{
Console.WriteLine("Usage: {0} file.pl{1}where file.pl is the prolog file",
System.Diagnostics.Process.GetCurrentProcess().ProcessName, Environment.NewLine);
return;
}
PrologInterface sharp = new PrologInterface();
SymbolTerm file = SymbolTerm.MakeSymbol(Path.GetFileNameWithoutExtension(args[0]));
Predicate member = new Compile_1(file, new ReturnCs(sharp));
sharp.SetPredicate(member);
bool b = sharp.Call();
}
}
}
Oczywiście możemy wykorzystać ten programik w ramach ‘Pre-Build events’, czy też w ramach wspomnianego obok ‘CustomTool dla Visual Studio’ i zautomatyzować sobie generację kodu C#. Należy oczywiście pamiętać o odświeżeniu projektu i dołączeniu wygenerowanych plików (sugeruję utworzyć podkatalog ‘Prolog’ w strukturze projektu i tam trzymać pliki .pl Prologu i wygenerowane klasy C#).
W powyższym tekście nie opisywałem mechanizmów stojących za P# - zainteresowanych odsyłam do materiałów związanych ze wspomnianą dysertacją Jonathana Cooka.Poza możliwością pracy równoległej P# udostępnia wiele różnych mechanizmów. Naprawdę warto zajrzeć!