Основы Системы Visual Prolog
Основы Visual Prolog |
---|
В этом руководстве мы представляем программу, разработанную на платформе системы программирования Visual Prolog. Алгоритмы, используемые в руководстве , - те же, что и в руководстве "Fundamental Prolog" (Часть 2).
Отличия между Visual Prolog и традиционным Прологом
Различия между традиционным Прологом и Visual Prolog можно провести по следующим категориям:
- Различия в структуре программы:
Различия между Visual Prolog и традиционным Прологом имеются, но они не существенны. Они сводятся к пониманию того, как различаются декларации (declarations) от определений (definitions), а также к явному выделению цели Goal. Все это поддержано специальными ключевыми словами.
- Файловая структура программы::
Visual Prolog предоставляет возможности структуризации программ с использованием файлов различного типа.
- Границы видимости:
Программа системы Visual Prolog может поддерживать функционирование, располагающееся в различных модулях с использованием концепции идентификация пространств.
- Объектная ориентированность:
Программа на языке Visual Prolog может быть написана как объектно ориентированная программа с использованием классических свойств объектно-ориентированной парадигмы.
Различия в структуре программ
Декларации и Определения
В Прологе, когда необходимо использовать предикат, то это делается без каких-либо предварительных указаний движку Пролога о таких намерениях. Например, в предыдущих руководствах клауз предиката grandFather (дедушка) был непосредственно записан с использованием традиционной для Пролога конструкции голова-тело. Мы не беспокоились о том, чтобы в тексте программы предупредить движок явно, что такая конструкция придиката потребуется.
Аналогично, когда в традиционном Прологе требуется использовать составной домен, мы можем его использовать без предупреждения движка по поводу этого намерения. Мы просто используем домен тогда, когда в этом возникает необходимость.
Однако, в Visual Prolog, перед написанием кода для тела клауза предиката мы должны сначала объявить о существовании такого предиката компилятору. Аналогично, перед использованием любых доменов они должны быть объявлены и представлены компилятору.
Причиной такой необходимости в предупреждениях является попытка как можно раньше обнаружить возможность исключений периода исполнения.
Под "исключениями периода исполнения (runtime exceptions)", мы понимаем события, возникающие только во время исполнения программы. Например, если Вы вознамерились использовать целое число в качестве аргумента функтора, а вместо этого вы по ошибке использовали вещественное число, то в процессе исполнения возникла бы ошибка периода исполнения (в программах, написанных для ряда компиляторов, не не для Visual Prolog) и программа в этом случае завершилась бы неуспешно.
Когда Вы объявляете предикаты или доменты, которые определены, то появляется своего рода позиционная грамматика (какому домену принадлежит какой аргумент), доступная компилятору. Более того, когда Visual Prolog выполняет компиляцию, он тщательно проверяет программу на наличие таких грамматических ошибок, наряду с другими.
Благодяря этому свойству Visual Prolog, повышается конечная эффективность программиста. Программист не должен ждать, когда реально работаящая программа совершит ошибку. Действительно, те из Вас, кто имеет опыт в программировании, понимает насколько этот код безопасен. Часто конкретная последовательность событий, приводящая к исключению периода исполнения кажется настолько неуловимой, что ошибка может проявиться через много лет, или она может заявить о себе в критической ситуации и привести к непредсказуемым последствиям!
Все это автоматически ведет к тому, что компилятор должен получать точные инструкции по поводу предикатов и доменов в виде соответствующих объявлений, которые должны предшествовать определениям.
Ключевые слова
Программа на Visual Prolog, представляемая кодом, разделяется ключевыми словами на секции разного вида путем использования ключевых слов, предписывающих компилятору, какой код генерировать. К примеру, есть ключевые слова, обозначающие различие между декларациями и определениями предикатов и доменов. Обычно, каждой секции предшествует ключевое слово. Ключевых слов, обозначающих окончание секции, нет. Наличие другого ключевого слова обозначает окончание предыдущей секции и начало другой.
Исключением из этого правила являются ключевые слова implement и end implement. Код, содержащийся между этими ключевыми словами, есть код, который относится к конкретному классу. Те, кто не понимает концепцию класса, может пока (в рамках этого руководства) представлять себе его как модуль или раздел какой-то программы более высокого уровня.
В рамках этого руководства мы представляем только часть ключевых слов, приведенных ниже. Мы также объясняем назначение этих ключевых слов, а конкретный синтаксис легко может быть изучен по документации. В Visual Prolog есть и другие ключевые слова, они упоминаются в других рукодствах.
В этом руководстве используются следующие ключевые слова:
implement и end implement
- Среди всех ключевых слов, обсуждаемых здесь, это единственные ключевые слова, используемые парно. Visual Prolog рассматривает код, помещенный между этими ключевыми словами, как код, принадлежащий одному классу. За ключевым словом implement обязательно ДОЛЖНО следовать имя класса.
open
- Это ключевое слово используется для расширения области видимости класса. Оно должно быть помещено вначале кода класса, сразу после ключевого слова implement (с именем класса и, возможно именем интерфейса - прим. перев.).
constants
- Это ключевое слово исползуется для обозначения секции кода, котора определяет неоднократно используемые значения, применяемые в коде. Например, если строковый литерал "PDC Prolog" предполагается использовать в различных местах кода, тогда можно единожды определить мнемоническое (краткое, легко запоминаемое слово) для использовани в таких местах:
constants pdc="PDC Prolog".
- Заметьте, что определение константы завершается точкой (.). В отличие от переменных Пролога константы должны начинаться со строчной буквы (нижний регистр).
domains
- Это ключевое слово используется для обозначения секции, объявляющей домены, которые будут использованы в коде. Синтаксис таких объявлений позволяет порождать множество вариантов объявлений доменов, используемых в тексте программы. В пределах этого руководства начального уровня мы не будем вдаваться в детали объаявлений доменов.
- Вообще говоря, объявляется функтор, который будет использоваться в качестве домена и ряд доменов, которые используются в качестве его аргументов. Функторы и составные домены рассматриваются в соответствующем руководстве.
class facts
- Это ключевое слово представляет секцию, в которой объявляются факты, которые будут в дальнейшем использоваться в тексте программы. Каждый факт объявляется как имя, используемое для обозначения факта, и набор аргументов, каждый из которых должен соответствовать либо стандартному (предопределенному), либо объявленному домену.
class predicates
- Эта секция содержит объявления предикатов, которые определяются в тексте программы в разделе clauses. И опять, объявление предиката - это имя, которое присваивается предикату, и набор аргументов, каждый из которых должен соответствовать либо стандартному (предопределенному), либо объявленному домену.
clauses
- Среди всех разделов, существующих в тексте программ на Visual Prolog, это единственный раздел, который близко совпадает с традиционными программами на Прологе. Он содержит конкретные определения объявленных в разделе class predicates предикатов, причем синтаксически им соответствующие.
goal
- Этот раздел определяет главную точку входа в программу на языке системы Visual Prolog. Более детальное описание приводится ниже.
Goal (цель)
В традиционном Прологе, как только какой-либо предикат определен в тексте, Пролог-машине может быть предписано выполнение программы, начиная с этого предиката. В Visual Prolog это не так. Будучи компилятором, он отвечает за генерацию эффективного исполняемого кода написанной программы. Этот код исполняется не в то же время, когда компилятор порождает код. Поэтому компилятору надо знать заранее точно, с какого предиката начнется исполнение программы. Благодаря этому, когда программа вызывается на исполнение, она начинает работу с правильной начальной точки. Как можно уже догадаться, откомпилированная программа может работать независимо от компилятора Visual Prolog и без участия среды IDE.
Для того, чтобы это стало возможным, введен специальный раздел, обозначенный ключевым словом Goal. Его можно представлять как особый предикат, не имеющий аргументов. Это предикат - именно тот предикат, с которого вся программа начинает исполняться.
Файловое структурирование программ
Необходимость помещения всех частей большой программы в один файл несомненно является неудобством. Это ведет к тому, что программа становится нечитаемой и иногда неправильной. Visual Prolog предусматривает возможность деления текста программы на отдельные файлы, используя среду IDE (Integrated Development Environment) и, кроме того, IDE позволяет помещать логически связанные фрагменты текста программы в отдельные файлы. Несмотря на такое разделение программы на части, сущности, находящиеся в общем использовании, доступны.
Например, если имеется домен, который используется несколькими файлами, то объявление домена делается в отдельном файле и к этому файлу из других файлов существует доступ.
Для упрощения этого руководства мы будем использовать один файл для разработки текста программы. На самом деле в процессе построения программы среда IDE создавала бы автоматически несколько файлов, но мы сейчас будем это умалчивать. Об этом написано в других руководствах.
Расширение Области Видимости
В Visual Prolog текст программы разделен на отдельные части, какждая часть определяется как класс (class). В объектно-ориентированных языках программирования, класс - это пакет кода и ассоциированные с ним данные. Это требует длительных объяснений и сделано в других руководствах. Те, кто не знаком с объектно-ориентированным программированием, может представлять себе класс нестрого как синоним понятия модуль. В Visual Prolog каждый класс обычно помещается в отдельный файл.
В процессе исполнения программы, часто бывает так, что программе может потребоваться вызвать предикат, который определен в другом классе (файле). Аналогично, данные (константы) или домены могут быть востребованы в другом файле.
Visual Prolog позволяет делать такие взаимные ссылки на предикаты или данные используя так называемую концепцию области видимости (scope access). Это может стать понятным на примере. Предположим имеется предикат, называемый pred1 и определенный в классе называемом class1 (который помещается в другом файле, согласно стратегии среды IDE), и мы хотим вызвать этот предикат в теле клауза некоторого другого предиката pred2, находящегося в другом файле (скажем, class2). Тогда вот как предикат pred1 должен бы быть вызван в теле клауза предиката pred2 :
clauses pred3:- ... !. pred2:- class1::pred1, % pred1 неизвестен в этом файле. % Он определен в другом файле, % поэтому требуется квалификатор класса. pred3, ...
В приведенном примере видно, что тело клауза предиката pred2 вызывает два предиката pred1 и pred3. Поскольку pred1 определен в другом файле (где определен класс class1), постольку слово class1 с символом :: (два двоеточия) предшествует слову pred1. Это можно назвать как квалификатор класса. Но предикат pred3 определен внутри того же самого файла, что и предикат pred2. Поэтому здесь нет необходимости использовать class2:: перед вызовом предиката pred3.
Технически такое поведение объясняется следующим: Область видимости (scope visibility) предиката pred3 находится внутри той же области, где находится предикат pred2. Поэтому здесь не нужно показывать, что pred3 и pred2 находятся в одном классе. Компилятор автоматически увидит определение предиката pred3 внутри той же области, в классе class2.
Область видимости конкретного класса лежит внутри границ, где класс определен (то есть в интервале между ключевыми словами implement - end implement ). Предикаты, определенные здесь, могут вызывать друг друга без квалификатора класса и двойного двоеточия (::).
Область видимости класса может быть расширена использованием ключевого слова open. Это ключевое слово информирует компилятор о том, что в этот класс должны быть "доставлены" имена (предикатов / констант / доменов), которые были определены в других файлах. Если область видимости расширена, то необходимости использования квалификатора класса (с двойным двоеточием) нет.
open class1 ... clauses pred3:- ... !. pred2:- pred1, % Внимание: квалификатор "class1::" больше не нужен, % поскольку область видимости расширена % использованием ключевого слова 'open' pred3, ...
Объектная ориентированность
Visual Prolog является полностью объектно-ориентированным языком.
Весь код, который разрабатывается для программы, помещается в класс. Это происходит само собой, даже если Вы не интересуетесь объектными свойствами языка. Это свойство обнаруживается в примере, разбираемом в этом руководстве. Код помещается в класс, называемый "family1", несмотря на то, что мы не используем объекты, порождаемые этим классом. Кроме того, вы этом классе мы используем общедоступные предикаты, находящиеся в других классах.
В этом руководстве объектные свойства языка не используются. Объектные свойства языка используются в других руководствах.
Полностью работающий пример: family1.prj6
Сгрузить: [www.visual-prolog.com/vip70/tutorial/tut09/family1.zip] [<img http://www.visual-prolog.com/vip70/tutorial/tut09/images/tut11_dot.gif" border="0" width="1" height="1">] проекта используемого в этом руководстве.
Давайте теперь все полученные знания соберем вместе и создадим первую программу на языке системы Visual Prolog. Общая логика будет та же, что и рассмотренная в руководстве "Fundamental Prolog. Part 2". Текст программы приведен ниже. Его надо поместить в файл с именем family1.pro. Реальный текст программы мы напишем с помощью среды IDE (Integrated Development Environment) системы Visual Prolog. На самом деле файлов больше, чем требуется для этого руководства, но они будут созданы (и в дальнейшем будут поддерживаться) автоматически средой IDE. Пошаговая инструкция по использованию IDE (с фрагментами экрана) для разработки программы приводится ниже.
Но прежде всего ознакомимся с главным текстом программы:
implement family1 open core constants className = "family1". classVersion = "$JustDate: $$Revision: $". clauses classInfo(className, classVersion). domains gender = female(); male(). class facts - familyDB person : (string Name, gender Gender). parent : (string Person, string Parent). class predicates father : (string Person, string Father) nondeterm anyflow. clauses father(Person, Father) :- parent(Person, Father), person(Father, male()). class predicates grandFather : (string Person, string GrandFather) nondeterm anyflow. clauses grandFather(Person, GrandFather) :- parent(Person, Parent), father(Parent, GrandFather). class predicates ancestor : (string Person, string Ancestor) nondeterm anyflow. clauses ancestor(Person, Ancestor) :- parent(Person, Ancestor). ancestor(Person, Ancestor) :- parent(Person, P1), ancestor(P1, Ancestor). class predicates reconsult : (string FileName). clauses reconsult(FileName) :- retractFactDB( familyDB), file::consult(FileName, familyDB). clauses run():- console::init(), stdIO::write("Load data\n"), reconsult("fa.txt"), stdIO::write("\nfather test\n"), father(X, Y), stdIO::writef("% is the father of %\n", Y, X), fail. run():- stdIO::write("\ngrandFather test\n"), grandFather(X, Y), stdIO::writef("% is the grandfather of %\n", Y, X), fail. run():- stdIO::write("\nancestor of Pam test\n"), X = "Pam", ancestor(X, Y), stdIO::writef("% is the ancestor of %\n", Y, X), fail. run():- stdIO::write("End of test\n"). end implement family1 goal mainExe::run(family1::run).
Step 1: Create a New Console Project in the IDE
Step 1a. After starting the Visual Prolog IDE, click on the New menu item from the Project menu.
Step 1b. A dialog would be presented to you. Enter all the relevant information. Ensure that the UI Strategy is Console and NOT GUI.
Step 2: Build an Empty Project
Step 2a. When the project is just created, the IDE would display the following project window. At this point in time, it does not have any clue about what files the project is dependent on. It does, however create the basic source code files for the project.
Step 2b. Use the Build menu item of the Build menu, to compile and link the empty project to create an executable file which basically does not do anything. (At this point it time, it would not generate any errors either)
While building the project, take a look at the messages that are dynamically displayed in the Messages window of the IDE. (In the case the Messages window is not visible, you can turn it on using the Window menu of the IDE) You will notice that the IDE intelligently pulls in ALL the necessary PFC (Prolog Foundation Classes) modules into your project. PFC classes are those classes which contains the basic functionality on which your programs would be built.
Step 3: Populate the family1.pro with the Actual Code
Step 3a. The default code inserted by the IDE is very basic and does not do anything useful. You would need to delete the entire code and copy and paste the actual code of family1.pro (given in this tutorial) into that window.
Step 3b. After you perform the copy-paste operation, the family1.pro window would now look as shown below:
Step 4: Rebuild the Code
Re-invoke the Build menu command, and the IDE will notice that the source code has changed, and will take measures to appropriately recompile the correct sections. If you use the Build All menu command, then ALL the modules would be recompiled. For large programs this can consume some time. Build All is often used ONLY at the end of a series of smaller compilations; just to ensure that everything is in ship-shape order.
During the Build process, the IDE not only does the compilation; but it also determines whether the project may need any other missing PFC modules, and inserts those and re-starts the compilation process if required. This can be seen in the messages appearing in the Messages window, where you'll notice the IDE was forced to build the project twice because of some additional include statements.
Файл:FundamentalVisualProlog7.jpg
Step 5: Execute the Program
We now have our first true application complied using Visual Prolog. In order to test the application we've just compiled, you can run the program using the Build | Run in Window menu command. However, in our case this would result in an error. The reason is that our little program is trying to read a text file (fa.txt) for its functioning. That text file contains the actual data regarding persons which our program will process. We'll come to the syntax of that file a bit later.
We need to now write that text file using some text editor and place it in the same directory where the executable is now residing. Normally, the executable would reside in the sub-folder called exe from the main project folder.
Let us create such a file using the IDE itself. Use the File | New menu command. The Create Project Item window will appear. Select Text file from the list on the left hand side. Then select the appropriate directory where the file is to reside (The same directory where the executable file family1.exe is present). Then, give the filename fa.txt and click on the Create button of the dialog. Till the filename is given, the Create button would be grayed (disabled). Make sure that the checkbox: Add to project as module is checked on. The advantage of adding it to the project is that it would be available for any subsequent debugging, etc.
The contents of the file would be as follows:
clauses person("Judith",female()). person("Bill",male()). person("John",male()). person("Pam",female()). parent("John","Judith"). parent("Bill","John"). parent("Pam","Bill").
Though it is a data file, you would notice that the syntax of the file is VERY similar to regular Prolog code! Even though the program is now compiled and is now in a binary format; you can change the output by simply changing the crucial data the program is processing. And that data is written into this text file using the same syntax as Prolog. Thus Visual Prolog emulates the dynamic code writing capability of traditional Prolog. Albeit, not all features are available but at least complicated domains such as those in this example can definitely be given.
The syntax used in fa.txt MUST be compatible with the domain definitions of the program. E.g. The functor used for defining a person MUST be person and not anything else; else appropriate compound domain representing that functor would not get initialized. (We had covered the topic of functors and compound domains in an earlier tutorial).
Now, when you run the program using the Build | Run in the Window menu command, this is what you'll see:
The program processes the information in the file fa.txt and works out the logical sequence of the parentages of people given there.
A Round up of the Program
The logic of the program is very simple. We had discussed it in an earlier tutorial, where we explained how the correct representation of data can help yield the appropriate results in a Prolog program. Let us now discuss the manner in which this logic works.
At start up, the main predicate will be invoked directly. This in turns branches off to the run predicate. The first thing the run predicate will do is to read in the data which is written in the file fa.txt. Once the data is in place, the program systematically progresses from one test to another to process the data and write out the conclusions of that test onto the console.
The mechanisms for processing the data are standard mechanisms of backtracking and recursions. This tutorial is not the place for a detailed explanation of how Prolog works, but here is a short summary of the internal workings.
When the run predicate is calling the father predicate, it does not stop at the first satisfactory ending of the father predicate. The invoking of a fail at the end of the predicate, forces the Prolog engine to seek another pass through the father predicate. Such a behavior is called backtracking, because the Prolog engine literally seems to backtrack over code which had earlier executed.
This happens recursively (i.e. as a repetitive process or cyclically), and at each cycle the father predicate yields the next result. Eventually all the possible definitions of "fathers" given in the data are exhausted, and the run predicate would have no choice but carry over to the next clause body of the run predicate.
The father predicate (as well as some other predicates) has been declared to be non-deterministic. The keyword nondeterm can be used to declare non-deterministic predicates; i.e. predicates that can yield multiple answers using backtracking.
If the no keyword is used in a predicate declaration, then the predicate has got the procedure mode, which can give only one solution, and the father predicate will stop after yielding the first result and cannot be re-entered into. Also, one must be careful in the usage of the cut (!) in the clause bodies of such non-deterministic predicates. If there is a cut at the end of the clause body of the father predicate, yet again; the predicate will yield only one solution (even if it has been declared to be non-deterministic).
The anyflow flow-pattern tells the compiler that the parameters that are given for the predicate could be either free or bound, with no restrictions whatsoever. That means, with the same definition of the father predicate, it would be possible to both yield the father's name (in case the name of the offspring was bound) OR the offspring's name (in case the name of the father was bound). It can also handle situations where both are bound; in which case the predicate will check if such a relationship exists in the data.
And; to state the obvious; the anyflow flow pattern can also accommodate a situation where both the parameters of the father predicate are free variables. In such a case, the father predicate will yield the various combinations of father+offspring present in the data the program has learnt (through consulting the fa.txt file) to the enquirer. The last usage is what is performed in the run predicate, as seen in the code snippet below. You would notice that the variables X and Y given to the father predicate are not bound when the father predicate is called.
clauses run():- console::init(), stdIO::write("Load data\n"), reconsult("fa.txt"), stdIO::write("\nfather test\n"), father(X,Y), stdIO::writef("% is the father of %\n", Y, X), fail.
Conclusion
In this lesson we learnt that programs written in Visual Prolog are often very similar to those written in traditional Prolog. There are several keywords that are used to differentiate the various parts of a Visual Prolog source code. Though Visual Prolog is an object-oriented language, it is possible to develop code which avoids the language's object-oriented features. A complete console-based application (family1) was developed in this tutorial, which explains, how to create such a program.
We also found that it is possible to emulate the dynamic working of a traditional Prolog program by keeping part of the code as a data file outside the main binary compiled application. The syntax of such a data file closely follows Prolog syntax.