Программирование на языке пролог
Шрифт:
число_родителей(адам,0):-!.
число_родителей(ева,0) :-!.
число_родителей(Х,2).
то есть число родителей для адами еваравно 0, а для всех остальных равно 2. Если мы всегда используем наше определение предиката число_родителейдля определения числа родителей некоторого данного человека, то все идет нормально. Мы получаем
?- число_родителей(ева,Х).
X = 0; нет
?- число_родителей(джон,Х).
X = 2;
нет
и так далее, как это и требуется. Отсечение необходимо, чтобы предотвратить процесс возврата, который мог бы привести к третьему правилу в случае, когда человек – это адамили ева. Однако рассмотрим, что произойдет, если мы используем те же самые
?- число_родителей(ева,2).
да
Вам следует самостоятельно разобраться, почему так получается – это просто следствие стратегии, применяемой в Прологе для поиска в базе данных. Наша реализация обработки «остальных» случаев, основанная на использовании отсечения, просто больше не работает надлежащим образом. Существуют два способа изменить определение, которые позволили бы нам устранить указанный эффект:
число_родителей(адам,N):-!, N=0.
число_родителей(ева,N):-!, N=0.
число_родителей(Х,2).
или
число_родителей(адам,0).
число_родителей(ева,0).
число_родителей(Х,2):- X \= адам, X \= ева.
Конечно, эти определения по-прежнему не работают, если задать целевое утверждение вида
?- число_родителей(Х,Y).
ожидая, что возврат позволит перечислить все возможности. Таким образом, можно сделать следующий вывод:
Если вы вводите отсечения для того, чтобы обеспечить правильную работу программы для целевых утверждений определенной формы, то нет гарантии, что при появлении целевых утверждений иной формы будет происходить что-либо разумное. Отсюда следует, что надежное использование отсечения возможно лишь в том случае, когда вы имеете четкое представление о том, как ваши правила будут использоваться. Если характер использования правил меняется, то необходимо пересмотреть все случаи употребления отсечения.
ГЛАВА 5 ВВОД И ВЫВОД
В предыдущих главах фигурировал только один способ предоставления информации Пролог-программе – обращение к ней с вопросом. Точно так же единственный способ определить значение переменной на некотором этапе доказательства согласованности целевого утверждения с базой данных состоял в построении вопроса таким образом, чтобы Пролог-система напечатала ответ в виде «Х=ответ». В большинстве случаев такого непосредственного взаимодействия с программой посредством вопросов вполне достаточно, чтобы убедиться в том, что программа работает правильно. Однако во многих ситуациях удобно писать программу на Прологе так, чтобы она сама инициировала диалог с пользователем. Например, предположим, что имеется база данных, содержащая информацию о событиях, происходивших в мире в 16-м веке. Информация представлена в виде фактов, включающих дату события и его краткое содержание. Даты могут быть представлены как целые числа, а содержание – в виде списков атомов. Те атомы в списке, которые начинаются с прописной буквы, будут заключаться в одинарные кавычки, чтобы Пролог не принял их за переменные:
событие(1505, ['Начала','Евклида', переведены, на, латинский, язык]).
событие(1510, ['Начало', спора, между, 'Реучлином', и 'Пфефферкорном']).
событие(1523, [Кристиан, 'II', покинул, 'Данию']).
. . .
Теперь, для того чтобы узнать, что связано с конкретной датой, мы могли бы задать следующий вопрос:
?- событие(1505,Х).
на что Пролог напечатал бы ответ:
Х=['Начала', 'Евклида', переведены, на, латинский, язык]
Представление краткого содержания событий в виде списков атомов дает возможность определить дату событий по некоторым ключевым моментам, имевшим место. Например, рассмотрим предикат когда,который мы определим ниже. Целевое утверждение когда(Х, Y)доказуемо, если в заголовке события, имевшего место в году Y, упоминается X:
когда(Х,Y):- событие(Y,Z), принадлежит (X,Z).
?- когда(Кристиан,D).
D=1523
Один из недостатков использования списков атомов заключается в том, что их неудобно вводить в систему, особенно если атомы начинаются с прописной буквы. Другая возможность, которая имеет свои недостатки и преимущества,- это представлять названия
событие(1511, "Лютер посещает Рим").
событие(1521, "Генри III провозглашен защитником веры").
событие(1524, "Умер Васко да Гама").
событие(1529, "Берквин сожжен в Париже").
событие(1540, "Возобновление войны с Турцией").
. . .
Такая форма представления удобнее для ввода, но посмотрим, что произойдет, если задаться вопросом
?- событие(1524,X).
В ответ Пролог напечатает непонятный список кодов ASCII, соответствующих литерам строки, являющейся значением переменной X! Хотя список литер легче ввести в систему, механизм 'вопрос – ответ' Пролога не позволяет получить ясный ответ. Было бы намного удобнее, если бы вместо того, чтобы обращаться к Прологу с подобными вопросами, можно было написать программу, которая вначале спрашивает, какая дата вас интересует, а затем выводит содержание соответствующего события на терминал. При этом названия событий можно было бы представлять в желаемом виде. Для выполнения задач подобного сорта в Прологе существует ряд встроенных предикатов, которые печатают свои аргументы на терминале. Имеются также предикаты, которые ожидают, пока пользователь введет текст с клавиатуры терминала, и присваивают переменной в качестве значения введенный текст. С помощью этих предикатов программа может взаимодействовать с вами, принимая от вас данные и печатая для вас результат. Когда программа ждет от вас данные, будем говорить, что она читаетили вводитданные. Точно так же, когда программа печатает некоторый результат, будем говорить, что она выводитрезультат. В этой главе мы описываем различные методы ввода и вывода данных. Один из рассматриваемых примеров связан с печатью кратких содержаний событий из базы данных исторических событий, а в заключение будет приведена программа, воспринимающая предложения на естественном языке и преобразующая их в список констант, который впоследствии может быть подвергнут обработке другими программами. Эта преобразующая программа, названная ввести,может использоваться как некий «модуль», с помощью которого можно создавать программы для анализа предложений на естественном языке. Программы, выполняющие такой анализ, обсуждаются в последующих главах, особенно в гл. 9.
5.1. Ввод и вывод термов
5.1.1. Вывод термов
Наиболее удобный способ напечатать некоторый терм на дисплее терминала состоит, по-видимому, в использовании встроенного предиката write.Если значением переменной Xявляется терм, то появление цели write(X)вызовет печать этого терма на дисплее. В случае если переменная Xнеконкретизирована, будет напечатано некоторое уникальное имя, которое состоит из одних цифр (например, '_253'). Однако если две переменные «сцеплены» в пределах одного и того же аргумента предиката write,то им будет соответствовать одна и та же переменная. Предикат writeнельзя согласовать вновь. Этот предикат выполняется лишь один раз, и всякая попытка вновь согласовать его заканчивается неудачей. Нельзя ли использовать writeдля вывода краткого содержания исторических событий в нашем примере? Вспомните, что строка литер в действительности представляется как список кодов литер. Если бы такой список был выведен с помощью предиката write,то он был бы напечатан как заключенная в квадратные скобки последовательность целых чисел, разделенных запятыми!
Прежде чем мы познакомимся с первым примером использования предиката write,нам нужно описать еще два предиката. Встроенный предикат nlприменяется для перехода на новую строку при печати данных на дисплее. Название « nl» образовано от «new line»(новая строка). Как и write,предикат nlвыполняется только один раз. Следующий встроенный предикат tabиспользуется для печати пробелов на экране дисплея. Целевое утверждение tab(X)выполняется только раз и вызывает перемещение курсора на Xпозиций вправо. Предполагается, что значение переменной X– целое число. Возможно, выбор имени tabне очень удачен, так как в действительности этот предикат не имеет ничего общего с табуляцией на обычных пишущих машинках или на дисплеях терминалов.