вызвал бы следующую печать:
hi
there
Другой предикат, с которым мы уже познакомились, - это tab(X), печатающий X пробелов (ASCII код равен 32). Разумеется, переменной X должно быть присвоено целое число. Отметим, что предикат tab(X) мог бы быть определен так:
tab(0):- !.
tab(N):- put(32), M is N-1, tab(M).
Теперь мы можем определить предикат, который мы назовем печать_строки. Если значением переменной X является список кодов литер (строка), то целевое утверждение печать_строки напечатает этот список (строку), используя put для печати каждого элемента списка. Как и во всех подобных программах, граничным условием является появление пустого списка. Это условие мы и используем для завершения рекурсии. При непустом списке с помощью put печатается голова списка, а затем используем печать_строки – хвост списка:
печать_строки([]).
печать_строки([Н|Т]):- put(H), печать_строки(Т).
?- печать_строки(«Чарлз V отрекся от престола в Брюсселе»).
Чарлз V отрекся от престола в Брюсселе
Если мы решили представлять краткое содержание исторических событий как строки литер, а не как списки атомов, то такого определения вполне достаточно, чтобы печатать строки из базы данных для предиката событие.
5.2.2. Ввод литер
Для ввода литер, набираемых на клавиатуре терминала, могут быть использованы предикаты get0(X) и get(X). Эти предикаты всегда согласуются с базой данных, если их аргументы неконкре-тизированы, а попытка повторного согласования всегда неудачна. При обработке целей, включающих эти предикаты, ЭВМ ожидает до тех пор, пока пользователь не наберет на клавиатуре какую-либо литеру. Указанные предикаты немного различаются тем, что get0(X) присвоит X любую набранную на клавиатуре литеру независимо от ее вида. Напротив, get(X) пропустит все управляющие литеры и присвоит X в качестве значения первую печатаемую литеру. Как отмечалось в гл. 2, печатаемая литера – это литера, которая визуализируется на дисплее терминала.
Если X уже присвоено значение, то целевое утверждение get(X) пропустит все управляющие литеры и сравнит следующую за ними печатаемую литеру со значением X. Доказательство согласованности целевого утверждения зависит от результата этого сравнения. Целевое утверждение get0(X) сравнивает X со следующей литерой и в зависимости от совпадения считается согласованным с базой данных или нет.
В следующем разделе приводятся некоторые примеры с использованием предикатов для чтения литер. Заранее обращаем внимание читателя на те случаи, когда возникает необходимость в возврате за целевое утверждение get.
5.3. Ввод предложений
В этом разделе мы представим программу, которая вводит предложение с терминала и преобразует его в список атомов языка Пролог. В программе определяется предикат ввести, имеющий один аргумент. Программа должна уметь определять, где заканчивается одно вводимое слово и начинается следующее. Поэтому предположим, что слово состоит из нескольких букв, цифр или специальных литер. Буквы и цифры уже были представлены в разд. 2.1. Мы будем рассматривать одиночную кавычку ''' и дефис '-' как специальные литеры. Литеры
, ; : ? ! .
будут рассматриваться как отдельные слова. Все другие литеры являются разделителями между словами. Предложение считается законченным, когда встречается одно из слов '.', '!' или '?'. Прописные буквы автоматически преобразуются в строчные, так что одно и то же слово всегда превращается в один и тот же атом. В результате такого определения программа будет поддерживать диалог с пользователем, подобный следующему:
?- ввести(S).
The man, who is very rich, saw John's watch.
S = [the,man,',',who,is,very,rich,',',saw,'John's',watch,'.']
В действительности мы вставили в представление предложения дополнительные одинарные кавычки, чтобы выделить некоторые атомы.
Программа использует предикат get0 для ввода литер с терминала. Затруднение, связанное с предикатом get0, состоит в том, что если литера прочитана с терминала этим предикатом, то она «ушла навсегда» и никакое другое целевое утверждение get0 или попытка вновь доказать целевое утверждение get0 не позволит получить доступ к этой литере вновь. Поэтому следует избегать возврата за точку использования get0, если мы хотим избежать «потери» литеры, которую он читает. Например, следующая программа, которая должна вводить литеры и печатать их снова, заменяя литеры а на b (код литеры 97 на код 98), не будет работать:
выполнить:- заменить_литеру, выполнить.
заменить_литеру:- get0(X) = 97,!, put(98).
заменить_литеру:- get0(X), put(X).
Приведенную программу в любом случае нельзя считать хорошей, потому что она будет работать вечно. Однако рассмотрим эффект попытки доказать согласованность целевого утверждения заме-нить_литеру. Если первое правило определения предиката заме-нить_литеру используется для чтения литеры, код которой отличен от 97, то возврат приведет к тому, что будет сделана попытка воспользоваться вместо него вторым правилом. Однако согласование целевого утверждения get0(X) во втором правиле приведет к тому, что X будет конкретизирована следующей литерой. Это объясняется тем, что доказательство исходного целевого утверждения get0 было необратимым процессом. Таким образом, эта программа в действительности не печатала бы все литеры. Она даже иногда печатала бы литеры а.
Как же программа ввести преодолеет проблемы возврата при вводе? Ответ заключается в том, что программа конструируется таким образом, что она вводит литеры с опережением на одну литеру, а проверки литеры выполняются правилом, отличным от правила, в котором эта литера была прочитана. Если литера введена в каком-то месте программы и не может быть здесь же использована, то она возвращается обратно для возможного использования другими правилами. В соответствии со сказанным предикат для ввода одного слова читать_слово в действительности имеет три аргумента. Первый предназначен для литеры, которая была получена при последнем выполнении get0 где-либо в программе, но которую оказалось невозможным использовать в месте ее получения. Второй предназначен для атома, который будет создан для прочитанного слова. Последний аргумент предназначен для литеры, следующей во вводимом предложении сразу за прочитанным словом. Для того чтобы определить, где кончается слово, необходимо ввести литеру, следующую непосредственно за словом. Эта литера должна быть сохранена, потому что она может оказаться первой литерой другого слова.
Здесь приведен текст программы:
/* Прочитать предложение */
ввести([Сл|Слс]):- get0(C), читать_слово(С,Сл,С1), остаток_предложения(Сл, С1, Слс).
/* Дано слово и литера после него, ввести остаток предложения */
остаток_предложения (Сл,_,П):- последнее_слово (Сл),!.
остаток_предложения(Сл,С,[Сл1|Слс]):- читать_слово(С, Сл, С1), остаток_предложения(Сл1,С1,Слс).
/* Ввести одно слово, имея начальную литеру и запомнив, какая литера идет после слова */
читать_слово(С,Сл,С1):- литера(С),!, name(Сл,С), get0(C1).
читать_слово(С,Сл,С2):- слово(С, Нс),!,get0(Cl),
остаток_слова(С1,Сс,С2),name(Сл,[Нс|Сс]).
читать_слово(С,Сл,С2):-get0(Cl), читать_слово (С1, Сл,С2).
остаток_слова(С,[Нс|Сс],С2):-слово(С,Нс),!,get0(Cl),остаток_слова (С1, Сс, С2). остаток_слова(С, [],С).