Основы Системы Visual Prolog: различия между версиями
(не показано 55 промежуточных версий этого же участника) | |||
Строка 47: | Строка 47: | ||
'''implement''' и '''end implement''' | '''implement''' и '''end implement''' | ||
:Среди всех ключевых слов, обсуждаемых здесь, это единственные ключевые слова, используемые парно. Visual Prolog рассматривает код, помещенный между этими ключевыми словами, как код, принадлежащий одному классу. За ключевым словом '''implement''' обязательно ДОЛЖНО следовать имя класса. | |||
'''open''' | '''open''' | ||
:Это ключевое слово используется для расширения '''области видимости''' класса. Оно должно быть помещено вначале кода класса, сразу после ключевого слова '''implement''' (с именем класса и, возможно именем интерфейса - прим. перев.). | |||
'''constants''' | '''constants''' | ||
:Это ключевое слово исползуется для обозначения секции кода, котора определяет неоднократно используемые значения, применяемые в коде. Например, если строковый литерал "PDC Prolog" предполагается использовать в различных местах кода, тогда можно единожды определить мнемоническое (краткое, легко запоминаемое слово) для использовани в таких местах: | |||
<blockquote> | |||
<vip>constants | <vip>constants | ||
pdc="PDC Prolog".</vip> | pdc="PDC Prolog".</vip> | ||
</blockquote> | |||
:Заметьте, что определение константы завершается точкой (.). В отличие от переменных Пролога константы должны начинаться со строчной буквы (нижний регистр). | |||
'''domains''' | '''domains''' | ||
:Это ключевое слово используется для обозначения секции, объявляющей домены, которые будут использованы в коде. Синтаксис таких объявлений позволяет порождать множество вариантов объявлений доменов, используемых в тексте программы. В пределах этого руководства начального уровня мы не будем вдаваться в детали объаявлений доменов. | |||
:Вообще говоря, объявляется функтор, который будет использоваться в качестве домена и ряд доменов, которые используются в качестве его аргументов. Функторы и составные домены рассматриваются в соответствующем руководстве. | |||
'''class facts''' | '''class facts''' | ||
:Это ключевое слово представляет секцию, в которой объявляются факты, которые будут в дальнейшем использоваться в тексте программы. Каждый факт объявляется как имя, используемое для обозначения факта, и набор аргументов, каждый из которых должен соответствовать либо стандартному (предопределенному), либо объявленному домену. | |||
'''class predicates''' | '''class predicates''' | ||
:Эта секция содержит объявления предикатов, которые определяются в тексте программы в разделе clauses. И опять, объявление предиката - это имя, которое присваивается предикату, и набор аргументов, каждый из которых должен соответствовать либо стандартному (предопределенному), либо объявленному домену. | |||
'''clauses''' | '''clauses''' | ||
:Среди всех разделов, существующих в тексте программ на Visual Prolog, это единственный раздел, который близко совпадает с традиционными программами на Прологе. Он содержит конкретные определения объявленных в разделе '''class predicates''' предикатов, причем синтаксически им соответствующие. | |||
'''goal''' | '''goal''' | ||
:Этот раздел определяет главную точку входа в программу на языке системы Visual Prolog. Более детальное описание приводится ниже. | |||
====Goal (цель)==== | |||
В традиционном Прологе, как только какой-либо предикат определен в тексте, Пролог-машине может быть предписано выполнение программы, начиная с этого предиката. В Visual Prolog это не так. Будучи ''компилятором'', он отвечает за генерацию эффективного исполняемого кода написанной программы. Этот код исполняется не в то же время, когда ''компилятор'' порождает код. Поэтому ''компилятору'' надо знать заранее точно, с какого предиката начнется исполнение программы. Благодаря этому, когда программа вызывается на исполнение, она начинает работу с правильной начальной точки. Как можно уже догадаться, откомпилированная программа может работать независимо от компилятора Visual Prolog и без участия среды IDE. | |||
Для того, чтобы это стало возможным, введен специальный раздел, обозначенный ключевым словом ''Goal''. Его можно представлять как особый предикат, не имеющий аргументов. Это предикат - именно тот предикат, с которого вся программа начинает исполняться. | |||
===Файловое структурирование программ=== | |||
Необходимость помещения всех частей большой программы в один файл несомненно является неудобством. Это ведет к тому, что программа становится нечитаемой и иногда неправильной. Visual Prolog предусматривает возможность деления текста программы на отдельные файлы, используя среду IDE (Integrated Development Environment) и, кроме того, IDE позволяет помещать логически связанные фрагменты текста программы в отдельные файлы. Несмотря на такое разделение программы на части, сущности, находящиеся в общем использовании, доступны. | |||
Например, если имеется домен, который используется несколькими файлами, то объявление домена делается в отдельном файле и к этому файлу из других файлов существует доступ. | |||
Для упрощения этого руководства мы будем использовать один файл для разработки текста программы. На самом деле в процессе построения программы среда IDE создавала бы автоматически несколько файлов, но мы сейчас будем это умалчивать. Об этом написано в других руководствах. | |||
=== | ===Расширение Области Видимости=== | ||
Visual Prolog | В Visual Prolog текст программы разделен на отдельные части, какждая часть определяется как класс (class). В объектно-ориентированных языках программирования, класс - это пакет кода и ассоциированные с ним данные. Это требует длительных объяснений и сделано в других руководствах. Те, кто не знаком с объектно-ориентированным программированием, может представлять себе '''класс''' нестрого как синоним понятия '''модуль'''. В Visual Prolog каждый класс обычно помещается в отдельный файл. | ||
В процессе исполнения программы, часто бывает так, что программе может потребоваться вызвать предикат, который определен в другом классе (файле). Аналогично, данные (константы) или домены могут быть востребованы в другом файле. | |||
Visual Prolog | Visual Prolog позволяет делать такие взаимные ссылки на предикаты или данные используя так называемую концепцию области видимости (scope access). Это может стать понятным на примере. Предположим имеется предикат, называемый ''pred1'' и определенный в классе называемом ''class1'' (который помещается в другом файле, согласно стратегии среды IDE), и мы хотим вызвать этот предикат в теле клауза некоторого другого предиката ''pred2'', находящегося в другом файле (скажем, ''class2''). Тогда вот как предикат ''pred1'' должен бы быть вызван в теле клауза предиката ''pred2'' : | ||
<vip>clauses | <vip>clauses | ||
pred3:- | |||
... | |||
!. | |||
pred2:- | |||
class1::pred1, % pred1 неизвестен в этом файле. | |||
% Он определен в другом файле, | |||
% поэтому требуется квалификатор класса. | |||
pred3, | |||
... | |||
</vip> | |||
В приведенном примере видно, что тело клауза предиката ''pred2'' вызывает два предиката ''pred1'' и ''pred3''. Поскольку ''pred1'' определен в другом файле (где определен класс ''class1''), постольку слово ''class1'' с символом '''::''' (два двоеточия) предшествует слову ''pred1''. Это можно назвать как квалификатор класса. Но предикат ''pred3'' определен внутри того же самого файла, что и предикат ''pred2''. Поэтому здесь нет необходимости использовать ''class2::'' перед вызовом предиката ''pred3''. | |||
Технически такое поведение объясняется следующим: ''Область видимости (scope visibility)'' предиката ''pred3'' находится внутри той же области, где находится предикат ''pred2''. Поэтому здесь не нужно показывать, что ''pred3'' и ''pred2'' находятся в одном классе. Компилятор автоматически увидит определение предиката ''pred3'' внутри той же области, в классе ''class2''. | |||
Область видимости конкретного класса лежит внутри границ, где класс определен (то есть в интервале между ключевыми словами '''implement''' - '''end implement''' ). Предикаты, определенные здесь, могут вызывать друг друга без квалификатора класса и двойного двоеточия (::). | |||
Область видимости класса может быть расширена использованием ключевого слова '''open'''. Это ключевое слово информирует компилятор о том, что в этот класс должны быть "доставлены" имена (предикатов / констант / доменов), которые были определены в других файлах. Если область видимости расширена, то необходимости использования квалификатора класса (с двойным двоеточием) нет. | |||
<vip>open class1 | <vip> | ||
open class1 | |||
... | |||
clauses | clauses | ||
pred3:- | |||
... | |||
!. | |||
pred2:- | |||
pred1, % Внимание: квалификатор "class1::" больше не нужен, | |||
% поскольку область видимости расширена | |||
% использованием ключевого слова 'open' | |||
pred3, | |||
...</vip> | |||
=== | ===Объектная ориентированность=== | ||
Visual Prolog является полностью объектно-ориентированным языком. | |||
Весь код, который разрабатывается для программы, помещается в класс. Это происходит само собой, даже если Вы не интересуетесь объектными свойствами языка. Это свойство обнаруживается в примере, разбираемом в этом руководстве. Код помещается в класс, называемый "family1", несмотря на то, что мы не используем объекты, порождаемые этим классом. Кроме того, вы этом классе мы используем общедоступные предикаты, находящиеся в других классах. | |||
В этом руководстве объектные свойства языка не используются. Объектные свойства языка используются в других руководствах. | |||
== | ==Полностью работающий пример: family1.prj6== | ||
''' | '''Сгрузитe:''' [http://www.visual-prolog.com/vip70/tutorial/tut09/family1.zip Исходные Файлы] проекта, используемого в этом руководстве. | ||
Давайте теперь все полученные знания соберем вместе и создадим первую программу на языке системы Visual Prolog. Общая логика будет та же, что и рассмотренная в руководстве "Fundamental Prolog. Part 2". Текст программы приведен ниже. Его надо поместить в файл с именем ''family1.pro''. Реальный текст программы мы напишем с помощью среды IDE (Integrated Development Environment) системы Visual Prolog. На самом деле файлов больше, чем требуется для этого руководства, но они будут созданы (и в дальнейшем будут поддерживаться) автоматически средой IDE. Пошаговая инструкция по использованию IDE (с фрагментами экрана) для разработки программы приводится ниже. | |||
Но прежде всего ознакомимся с главным текстом программы: | |||
<vip>implement family1 | <vip>implement family1 | ||
open core | |||
constants | constants | ||
className = "family1". | |||
classVersion = "$JustDate: $$Revision: $". | |||
clauses | clauses | ||
classInfo(className, classVersion). | |||
domains | domains | ||
gender = female(); male(). | |||
class facts - familyDB | class facts - familyDB | ||
person : (string Name, gender Gender). | |||
parent : (string Person, string Parent). | |||
class predicates | class predicates | ||
father : (string Person, string Father) nondeterm anyflow. | |||
clauses | clauses | ||
father(Person, Father) :- | |||
parent(Person, Father), | |||
person(Father, male()). | |||
class predicates | class predicates | ||
grandFather : (string Person, string GrandFather) nondeterm anyflow. | |||
clauses | clauses | ||
grandFather(Person, GrandFather) :- | |||
parent(Person, Parent), | |||
father(Parent, GrandFather). | |||
class predicates | class predicates | ||
ancestor : (string Person, string Ancestor) nondeterm anyflow. | |||
clauses | clauses | ||
ancestor(Person, Ancestor) :- | |||
parent(Person, Ancestor). | |||
ancestor(Person, Ancestor) :- | |||
parent(Person, P1), | |||
ancestor(P1, Ancestor). | |||
class predicates | class predicates | ||
reconsult : (string FileName). | |||
clauses | clauses | ||
reconsult(FileName) :- | |||
retractFactDB( familyDB), | |||
file::consult(FileName, familyDB). | |||
clauses | 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 | end implement family1 | ||
Строка 223: | Строка 233: | ||
mainExe::run(family1::run).</vip> | mainExe::run(family1::run).</vip> | ||
=== | ===Шаг 1: Создайте в IDE новый консольный проект=== | ||
''' | '''Шаг 1a.''' После старта среды программирования Visual Prolog, выберите ''New'' из меню ''Project''. | ||
[[Image:FundamentalVisualProlog1.jpg]] | [[Image:FundamentalVisualProlog1.jpg]] | ||
''' | '''Шаг 1b.''' Появляется диалог. Введите соответствующую информацию. Удостоверьтесь, что стратегия пользовательского интерфейса (UI Strategy) ''Console'', а '''НЕ''' ''GUI''. | ||
[[Image:FundamentalVisualProlog2.jpg]] | [[Image:FundamentalVisualProlog2.jpg]] | ||
=== | ===Шаг 2: Постройте пустой проект=== | ||
''' | '''Шаг 2a.''' Когда проект только что создан, среда будет показывать проектное окно, как показано ниже. В этот момент пока никакой информации о том, от каких файлов зависит проект, нет. Однако основные тексты программ проектных файлов уже созданы. | ||
''' | '''Шаг 2b.''' Выберите из меню ''Build'' позицию ''Build''. Пустой проект, который ничего не делает, будет построен. (В этот момент времени никаких сообщений о ошибках быть не должно). | ||
В процессе построения проекта посмотрите на сообщения, которые динамически появляются в окне '''Messages''' среды IDE. (Если окно '''Messages''' не видно, его можно вызвать на передний план, используя меню Window в среде IDE). Вы заметите, что среда IDE разумно включит в Ваш проект ВСЕ в необходимые модули PFC (Prolog Foundation Classes). Классы PFC являются теми классами, которые содержат поддержку функционирования программ, которые Вы строите. | |||
=== | ===Шаг 3: Поместите в файл family1.pro актуальный код=== | ||
''' | '''Шаг 3a.''' Начальный код, созданный самой средой IDE имеет самый общий вид и сам по себе не делает ничего полезного. Вам необходимо удалить этот код полностью и скопировать (через Copy и Paste) полностью (актуальный на данный момент) код программы family1.pro (приведенный в этом руководстве) в окно редактора. | ||
[[Image:FundamentalVisualProlog5.jpg]] | [[Image:FundamentalVisualProlog5.jpg]] | ||
''' | '''Шаг 3b.''' После выполнения операции copy-paste окошко с текстом файла family1.pro должно выглядеть так, как показано ниже: | ||
[[Image:FundamentalVisualProlog6.jpg]] | [[Image:FundamentalVisualProlog6.jpg]] | ||
=== | ===Шаг 4: Перестроение кода проекта=== | ||
Повторите вызов команды меню '''Build'''. IDE теперь уведомит, что исходный текст поменялся, и выполнит все необходимые действия для перекомпиляции соответствующих разделов. Если Вы вызовите команду меню Rebuild All, тогда ''ВСЕ'' модули проекта будут перекомпилированы. В случае больших проектов это может занимать значительное время. Rebuild All обычно используется в качестве финальной фазы после ряда небольших изменений и соответствующих компиляций, дабы удостовериться в том, что все в полном порядке. | |||
В процессе выполнения команды ''Build'' среда IDE выполняет не только компиляцию. В это же время выясняется, нужны ли проекту другие модули из набора PFC (Prolog Faundation Classes), и, если это так, то такие модули добавляются и вызывается повторная перекомпиляция, если необходимо. Этот процесс можно наблюдать по сообщениям, появляющимся в окне сообщений (Message Window). Можно увидеть как IDE вызвало построение проекта дважды, поскольку были обнаружены директивы "include", повлиявшие на это. | |||
[[Image:FundamentalVisualProlog7.jpg]] | [[Image:FundamentalVisualProlog7.jpg]] | ||
=== | ===Шаг 5: Исполнение Программы=== | ||
Теперь у нас есть приложение, успешно откомпилированное с помощью Visual Prolog. Для того, чтобы проверить работу приложения, которое только что построено, можно запустить ее выполнение, вызвав команду из меню ''Build | Run in Window''. Но в нашем случае это приведет к ошибке. Причина заключается в том, что наша маленькая программа для обеспечения своей работы пытается читать текстовый файл ''fa.txt''. Этот текстовый файл содержит содержит записи относительно лиц, которые (записи) программа должна обработать. Вопросов синтаксита представления данных в этом файле мы коснемся позже. | |||
Сейчас нам необходимо написать этот текстовый файл с использованием текстового редактора и поместить его в ту же директорию, где располагается сама исполняемая программа. Обычно исполняемая программа помещается в поддиректории проектной директории, имеющей имя EXE. | |||
Давайте создадим такой файл сами с использованием среды IDE. Вызовите команду меню ''File | New''. Появляется окно создания новой сущности ''Create Project Item''. Выберите Текстовый файл (Text File) в списке слева. Затем выберите директорию, где будет размещаться файл (та же директория, где располагается исполняемое приложение family1.exe). Присвойте теперь имя ''fa.txt'' файлу и нажмите кнопку Create (создать). До тех пор, пока имя файла не введено, кнопка Create (создать) будет неактивна (надпись серого цвета). Удостоверьтесь, что флажот ''Add to project as module'' (добавить в проект на правах модуля) включен. Полезность включения файла в проект заключается в том, что это будет удобно для последующей отладки и других действий. | |||
Файл следует заполнить текстом следующего содержания: | |||
<vip>clauses | <vip>clauses | ||
person("Judith",female()). | person("Judith",female()). | ||
person("Bill",male()). | |||
person("John",male()). | |||
person("Pam",female()). | |||
parent("John","Judith"). | |||
parent("Bill","John"). | |||
parent("Pam","Bill").</vip> | |||
Несмотря на то, что это файл данных, Вы заметите, что синтаксис файла очень похож на обычный код Пролога! Даже несмотря на то, что программа теперь откомпилирована и представлена в двоичном формате, Вы можете менять ход и результаты ее исполнения, изменяя данные, которые программа обрабатывает. И эти данные записываются в этот текстовый файл с использованием того же самого синтаксиса Пролога. Visual Prolog таким образом эмулирует свойство использования динамически изменяемого кода, характерное для традиционного пролога. Хотя не все свойства использования динамически изменяемого кода традиционного Пролога воспроизводятся, но достаточно сложные структуры данных (или не сложные, как в этом примере) могут быть использованы. | |||
Синтаксис файла fa.txt ДОЛЖЕН быть совместим с определениями доменов в программе. Например, функтор, применяемый для определения персоны ДОЛЖЕН быть ''person'' и никак иначе. В противном случае соответствующий составной домен не будет инициализирован. (Понятия функторов и составных доменов рассматриваются в других руководствах). | |||
Теперь, запустив программу по команде меню '''Build | Run in the Window''' вы увидите: | |||
[[Image:FundamentalVisualProlog8.jpg]] | [[Image:FundamentalVisualProlog8.jpg]] | ||
Программа обрабатывает данные, помещенные в файл fa.txt и порождает логические связи персон, данные о которых там помещены. | |||
=== | ===Рассмотрение программы=== | ||
Логика программы очень проста. Мы ее уже обсуждали в других руководствах, где рассматривалось как корректное представление данных может помочь построить эффективную программу на Прологе. Давайте теперь обратим внимание на логику используемого метода. | |||
При старте непосредственно будет вызван главный предикат. Здесь все построена вокруг предиката ''run''. Предикат ''run'' прежде всего зачитывает данные, записанные в файле ''fa.txt''. После загрузки данных, программа систематически переходит от одной проверки к другой, обрабатывая данные, и выводит на консоль выводы на базе этих проверок. | |||
Обработка данных основывается на стандартных механизмах отката и рекурсивных вызовов. В этом руководстве детально не рассматривается как работает Пролог, но приведем краткое описание работы. | |||
Когда предикат ''run'' вызывает предикат ''father'', он не ограничивается первым удовлетворяющим решением предиката ''father''. Применение предиката fail в конце последовательности понуждает механизм Пролога к поиску следующего прохода предиката ''father''. Такое поведение, называется ''backtracking'' (откат), поскольку кажется, что механизм Пролога буквально перепрыгивает назад, минуя ранее исполненный код. | |||
Это происходит рекурсивно (то есть как повтояющийся или циклический процесс) и в каждом цикле предикат ''father'' вырабатывает новый результат. В итоге все возможные определения "отцов", представленные данными исчерпываются и у предиката ''run'' нет другого выхода как перейти к следующему своему клаузу. | |||
Предикат ''father'' (так же как и ряд других предикатов) объявлен как нетерминированный. Для обозначения недетерминированных предикатов используется ключевое слово '''nondeterm''', обозначающее, что предикат может вырабатывать множество решений посредством бэктрекинга (отката). | |||
Если никакое ключевое слово в объявлении предиката не используется, то предикат получает режим процедуры, которая может выработать только одно решение, и предикат ''father'' прекратил бы работу после получения первого же результата и не смог бы быть вызван повторно. Заметим, следует быть очень осторожными в использовании отсечения (cut), обозначаемого как '''!''', в клаузах таких недетерминированных предикатов. Если в конце клаузы предиката ''father'' помещено отсечение, то предикат выработает только одно решение (даже если он объявлен как недетерминированный). | |||
Квалификатор направлений ввода-вывода ''anyflow'' говорит компилятору о том, что параметры, назначенные предикату могут быть как связанными, так и свободными, без каких либо ограничений. Это означает, что используя одну и ту же декларацию предиката ''father'', можно будет получать как имя отца (в случае, если имя потомка связано) так и имя потомка (в случае, если имя отца связано). Ситуация, когда оба параметра связаны, также обрабатывается - и в этом случае проверяется существует ли отношение между данными, представленными параметрами. | |||
И, наконец, квалификатор ''anyflow'' включает ситуацию, когда оба параметра предиката ''father'' являются свободными переменными. В этом случае, предикат ''father'' вернет в ответ на запрос различные комбинации отношений родитель-потомок, предусмтотренные в программе (представленные в загружаемом файле ''fa.txt''). Последний вариант как раз и использован в предикате ''run'', как видно во фрагменте, приведенном ниже. Можно заметить, что переменные X и Y переданные предикату ''father'' не связаны при его вызове. | |||
<vip>clauses | <vip>clauses | ||
Строка 318: | Строка 327: | ||
fail.</vip> | fail.</vip> | ||
== | ==Заключение== | ||
На этом материале мы познакомились с тем, что программы, написанные в системе программирования Visual Prolog, часто очень похожи на программы, написанные в традиционном Прологе. Некоторый набор ключевых слов используется для обозначения различий частей исходного кода на языке системы Visual Prolog. Хотя Visual Prolog является объектно-ориентированным языком, сохраняется возможность разработки кода, не использующего свойства объектной ориентированности. Было показано работающее приложение консольного типа, которое поясняет как создавать подобные программы. | |||
Мы увидели, кроме того, как эмулируется работа с динамическими данными, характерная для традиционного Пролога. Это делается путем помещения части кода в виде внешнего файла, отделенного от программы, представленной в двоичном формате. Синтаксис данных такого рода полностью соответствует синтаксису Пролога. | |||
==Ссылки== | |||
[[en:Fundamental Visual Prolog]] | |||
[[Категория:VipРуководства]] | [[Категория:VipРуководства]] |
Текущая версия на 09:00, 25 декабря 2007
Основы 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
Сгрузитe: Исходные Файлы проекта, используемого в этом руководстве.
Давайте теперь все полученные знания соберем вместе и создадим первую программу на языке системы 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).
Шаг 1: Создайте в IDE новый консольный проект
Шаг 1a. После старта среды программирования Visual Prolog, выберите New из меню Project.
Шаг 1b. Появляется диалог. Введите соответствующую информацию. Удостоверьтесь, что стратегия пользовательского интерфейса (UI Strategy) Console, а НЕ GUI.
Шаг 2: Постройте пустой проект
Шаг 2a. Когда проект только что создан, среда будет показывать проектное окно, как показано ниже. В этот момент пока никакой информации о том, от каких файлов зависит проект, нет. Однако основные тексты программ проектных файлов уже созданы.
Шаг 2b. Выберите из меню Build позицию Build. Пустой проект, который ничего не делает, будет построен. (В этот момент времени никаких сообщений о ошибках быть не должно).
В процессе построения проекта посмотрите на сообщения, которые динамически появляются в окне Messages среды IDE. (Если окно Messages не видно, его можно вызвать на передний план, используя меню Window в среде IDE). Вы заметите, что среда IDE разумно включит в Ваш проект ВСЕ в необходимые модули PFC (Prolog Foundation Classes). Классы PFC являются теми классами, которые содержат поддержку функционирования программ, которые Вы строите.
Шаг 3: Поместите в файл family1.pro актуальный код
Шаг 3a. Начальный код, созданный самой средой IDE имеет самый общий вид и сам по себе не делает ничего полезного. Вам необходимо удалить этот код полностью и скопировать (через Copy и Paste) полностью (актуальный на данный момент) код программы family1.pro (приведенный в этом руководстве) в окно редактора.
Шаг 3b. После выполнения операции copy-paste окошко с текстом файла family1.pro должно выглядеть так, как показано ниже:
Шаг 4: Перестроение кода проекта
Повторите вызов команды меню Build. IDE теперь уведомит, что исходный текст поменялся, и выполнит все необходимые действия для перекомпиляции соответствующих разделов. Если Вы вызовите команду меню Rebuild All, тогда ВСЕ модули проекта будут перекомпилированы. В случае больших проектов это может занимать значительное время. Rebuild All обычно используется в качестве финальной фазы после ряда небольших изменений и соответствующих компиляций, дабы удостовериться в том, что все в полном порядке.
В процессе выполнения команды Build среда IDE выполняет не только компиляцию. В это же время выясняется, нужны ли проекту другие модули из набора PFC (Prolog Faundation Classes), и, если это так, то такие модули добавляются и вызывается повторная перекомпиляция, если необходимо. Этот процесс можно наблюдать по сообщениям, появляющимся в окне сообщений (Message Window). Можно увидеть как IDE вызвало построение проекта дважды, поскольку были обнаружены директивы "include", повлиявшие на это.
Файл:FundamentalVisualProlog7.jpg
Шаг 5: Исполнение Программы
Теперь у нас есть приложение, успешно откомпилированное с помощью Visual Prolog. Для того, чтобы проверить работу приложения, которое только что построено, можно запустить ее выполнение, вызвав команду из меню Build | Run in Window. Но в нашем случае это приведет к ошибке. Причина заключается в том, что наша маленькая программа для обеспечения своей работы пытается читать текстовый файл fa.txt. Этот текстовый файл содержит содержит записи относительно лиц, которые (записи) программа должна обработать. Вопросов синтаксита представления данных в этом файле мы коснемся позже.
Сейчас нам необходимо написать этот текстовый файл с использованием текстового редактора и поместить его в ту же директорию, где располагается сама исполняемая программа. Обычно исполняемая программа помещается в поддиректории проектной директории, имеющей имя EXE.
Давайте создадим такой файл сами с использованием среды IDE. Вызовите команду меню File | New. Появляется окно создания новой сущности Create Project Item. Выберите Текстовый файл (Text File) в списке слева. Затем выберите директорию, где будет размещаться файл (та же директория, где располагается исполняемое приложение family1.exe). Присвойте теперь имя fa.txt файлу и нажмите кнопку Create (создать). До тех пор, пока имя файла не введено, кнопка Create (создать) будет неактивна (надпись серого цвета). Удостоверьтесь, что флажот Add to project as module (добавить в проект на правах модуля) включен. Полезность включения файла в проект заключается в том, что это будет удобно для последующей отладки и других действий.
Файл следует заполнить текстом следующего содержания:
clauses person("Judith",female()). person("Bill",male()). person("John",male()). person("Pam",female()). parent("John","Judith"). parent("Bill","John"). parent("Pam","Bill").
Несмотря на то, что это файл данных, Вы заметите, что синтаксис файла очень похож на обычный код Пролога! Даже несмотря на то, что программа теперь откомпилирована и представлена в двоичном формате, Вы можете менять ход и результаты ее исполнения, изменяя данные, которые программа обрабатывает. И эти данные записываются в этот текстовый файл с использованием того же самого синтаксиса Пролога. Visual Prolog таким образом эмулирует свойство использования динамически изменяемого кода, характерное для традиционного пролога. Хотя не все свойства использования динамически изменяемого кода традиционного Пролога воспроизводятся, но достаточно сложные структуры данных (или не сложные, как в этом примере) могут быть использованы.
Синтаксис файла fa.txt ДОЛЖЕН быть совместим с определениями доменов в программе. Например, функтор, применяемый для определения персоны ДОЛЖЕН быть person и никак иначе. В противном случае соответствующий составной домен не будет инициализирован. (Понятия функторов и составных доменов рассматриваются в других руководствах).
Теперь, запустив программу по команде меню Build | Run in the Window вы увидите:
Программа обрабатывает данные, помещенные в файл fa.txt и порождает логические связи персон, данные о которых там помещены.
Рассмотрение программы
Логика программы очень проста. Мы ее уже обсуждали в других руководствах, где рассматривалось как корректное представление данных может помочь построить эффективную программу на Прологе. Давайте теперь обратим внимание на логику используемого метода.
При старте непосредственно будет вызван главный предикат. Здесь все построена вокруг предиката run. Предикат run прежде всего зачитывает данные, записанные в файле fa.txt. После загрузки данных, программа систематически переходит от одной проверки к другой, обрабатывая данные, и выводит на консоль выводы на базе этих проверок.
Обработка данных основывается на стандартных механизмах отката и рекурсивных вызовов. В этом руководстве детально не рассматривается как работает Пролог, но приведем краткое описание работы.
Когда предикат run вызывает предикат father, он не ограничивается первым удовлетворяющим решением предиката father. Применение предиката fail в конце последовательности понуждает механизм Пролога к поиску следующего прохода предиката father. Такое поведение, называется backtracking (откат), поскольку кажется, что механизм Пролога буквально перепрыгивает назад, минуя ранее исполненный код.
Это происходит рекурсивно (то есть как повтояющийся или циклический процесс) и в каждом цикле предикат father вырабатывает новый результат. В итоге все возможные определения "отцов", представленные данными исчерпываются и у предиката run нет другого выхода как перейти к следующему своему клаузу.
Предикат father (так же как и ряд других предикатов) объявлен как нетерминированный. Для обозначения недетерминированных предикатов используется ключевое слово nondeterm, обозначающее, что предикат может вырабатывать множество решений посредством бэктрекинга (отката).
Если никакое ключевое слово в объявлении предиката не используется, то предикат получает режим процедуры, которая может выработать только одно решение, и предикат father прекратил бы работу после получения первого же результата и не смог бы быть вызван повторно. Заметим, следует быть очень осторожными в использовании отсечения (cut), обозначаемого как !, в клаузах таких недетерминированных предикатов. Если в конце клаузы предиката father помещено отсечение, то предикат выработает только одно решение (даже если он объявлен как недетерминированный).
Квалификатор направлений ввода-вывода anyflow говорит компилятору о том, что параметры, назначенные предикату могут быть как связанными, так и свободными, без каких либо ограничений. Это означает, что используя одну и ту же декларацию предиката father, можно будет получать как имя отца (в случае, если имя потомка связано) так и имя потомка (в случае, если имя отца связано). Ситуация, когда оба параметра связаны, также обрабатывается - и в этом случае проверяется существует ли отношение между данными, представленными параметрами.
И, наконец, квалификатор anyflow включает ситуацию, когда оба параметра предиката father являются свободными переменными. В этом случае, предикат father вернет в ответ на запрос различные комбинации отношений родитель-потомок, предусмтотренные в программе (представленные в загружаемом файле fa.txt). Последний вариант как раз и использован в предикате run, как видно во фрагменте, приведенном ниже. Можно заметить, что переменные X и Y переданные предикату father не связаны при его вызове.
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.
Заключение
На этом материале мы познакомились с тем, что программы, написанные в системе программирования Visual Prolog, часто очень похожи на программы, написанные в традиционном Прологе. Некоторый набор ключевых слов используется для обозначения различий частей исходного кода на языке системы Visual Prolog. Хотя Visual Prolog является объектно-ориентированным языком, сохраняется возможность разработки кода, не использующего свойства объектной ориентированности. Было показано работающее приложение консольного типа, которое поясняет как создавать подобные программы.
Мы увидели, кроме того, как эмулируется работа с динамическими данными, характерная для традиционного Пролога. Это делается путем помещения части кода в виде внешнего файла, отделенного от программы, представленной в двоичном формате. Синтаксис данных такого рода полностью соответствует синтаксису Пролога.