Рассмотрим определение предиката consult. Разумеется, предикат consult предусмотрен среди встроенных предикатов большинства Пролог-систем, однако интересно посмотреть, как он может быть определен на Прологе.
consult(Файл):-seeing(Input),sее(Файл),repeat,read(Tepм),обработать(Терм),seen,see(Input),!.
обработать(Терм):- маркер_конца_файла(Терм),!.
обработать((?- Q)):-!, call(Q),!, fail.
обработать(Утвержд):- assertz(Утвержд), fail.
Это определение отличается рядом интересных особенностей. Во-первых, цель seeing(Input) и ее партнер see(Input) призваны гарантировать, что текущий файл ввода не будет «забыт» после применения предикат consult. Во-вторых, предикат маркер_конца_файла здесь использован без определения. По замыслу он должен быть истинным только в том случае, когда его аргумент конкретизирован термом, используемым для представления конца файла (который мог бы встретиться при выполнении read). В разных реализациях Пролога для представления «конца файла» используются разные термы, поэтому маркер_конца_файла в разных реализациях может быть определен по-разному. Одно из возможных определений выглядит так:
маркер_конца_файла(конец_файла).
В определении предиката обработать интересна организация выполнения соответствующих действий для каждого терма, считанного из входного файла. Целевое утверждение обработать доказуемо только, когда его аргументом является маркер конца файла. Иначе после соответствующего действия имитируется неудача доказательства и инициируется механизм возврата, который возвращает программу к предикату repeat. Отметим важность «отсечения» в конце определения предиката consult. Оно фиксирует выбор, сделанный предикатом repeat[13]. И последнее замечание. Если терм, считанный из файла, представляет собой вопрос (см. второе утверждение определения предиката обработать), то делается попытка немедленно согласовать соответствующую цель с помощью предиката call (см. разд. 6.7).
В качестве примера использования предиката retract здесь приведено определение полезного предиката уберивсе. При согласовании с базой данных целевого утверждения уберивсе(Х) все утверждения, заголовки которых совпадают с X, удаляются из базы данных. Поскольку в данном определении используется предикат retract, то переменная X не может быть неконкретизированной, так как в противном случае не с чем будет сопоставлять утверждения из базы данных. Данное определение должно распознавать два вида утверждений с заголовками, совпадающими с X, - факты и правила. При обработке этих двух видов утверждений в вызове retract задаются разные аргументы. В определении используется то свойство, что retract будет срабатывать при возврате до тех пор, пока все утверждения, сопоставимые с его аргументами, не будут удалены из базы данных.
уберивсе(Х):- retract(X), fail.
уберивсе(Х):- retract((X:- Y)), fail. уберивсе(_).
В качестве примера использования предиката убери все здесь приведено определение предиката reconsult на Прологе. Назначение предиката reconsult сходно с назначением предиката consult, с той лишь разницей, что при reconsult каждое считанное утверждение замещает существующее утверждение того же предиката, а не добавляется к нему (см. разд. 6.1).
reconsult(Файл):-уберивсе(сделано(_)),seeing(Старый),sее(Файл),repeat,read(Терм),проверить(Терм),seen,see(Старый),!.
проверить(Х):- маркер_конца_файла(Х),!.
проверить((?- Цели)):-!, call (Цели),!, fail.
проверить(Утверждение):-заголовок(Утверждение, Заголовок), запись_сделана(3аголовок), assertz(Утверждение), fail.
запись_сделана(3аголовок):- сделано(Заголовок),!.
запись_сделана(3аголовок):- functor(Заголовок,Func,Arity), functor(Proc,Func,Arity), asserta(cдeлaнo(Proc)), уберивсе(Ргос),!.
заголовок((А:- В), А):-!.
заголовок (А, А).
Это определение похоже на определение consult, в котором вместо предиката обработать используется предикат проверить. Основное различие заключено в предикате запись_сделана. Когда в файле появляется первое утверждение для данного предиката, то, прежде чем к базе данных будет добавлено хотя бы одно новое утверждение, из нее должны быть удалены все старые утверждения для данного предиката. Удаление этих утверждений нельзя откладывать до момента, когда в базе данных появятся их новые версии, поскольку в этом случае мы удалили бы из базы данных те утверждения, которые только что были введены. Как же определить, что некоторое утверждение в файле является первым для соответствующего предиката? Ответ заключается в том, что мы регистрируем в базе данных предикаты, для которых уже нашлись утверждения в файле. Это делается с помощью предиката сделано. Когда из файла считывается первое утверждение например, для предиката foo с двумя переменными, то имеющиеся утверждения удаляются и новое утверждение добавляется к базе данных. Кроме того, к базе данных добавляется факт:
сделано(foo(_,_)).
В дальнейшем, при чтении остальных утверждений для предиката too, мы сможем проверить, что старые утверждения уже удалены из базы данных. Тем самым удается избежать ошибочного удаления новых утверждений. Для данного определения важно, что мы не добавляем в базу данных что-нибудь вроде:
сделано(foо(а,Х)).
поскольку в этом случае аргумент предиката сделано не обязательно совпадает с заголовком утверждения для foo. Пара запросов
…,functor(Заголовок,Func,Arity),functor(Proc,Func,Arity),…
конкретизирует Ргос структурой, имеющей тот же функтор, что и заголовок Заголовок, но с переменными в качестве аргументов (см. разд. 6.5).
ГЛАВА 8. ОТЛАДКА ПРОЛОГ-ПРОГРАММ
На приведенных выше примерах вы уже приобрели опыт применения программ и научились их изменять, а также успели написать и свои собственные программы. Теперь самое время заняться вопросом: что делать, когда программа ведет себя не так, как ожидалось. Подобные неожиданности связаны с наличием ошибок в программе, а процесс устранения этих ошибок называется отладкой. По нашему убеждению самым разумным подходом к программированию является тот, который может быть охарактеризован как «программирование с предупреждением ошибок». Перефразируя старую поговорку, можно сказать, что грамм тщательного программирования стоит килограмма отладки. В этой главе мы расскажем о некоторых методах отладки, но начнем с того, как избежать проникновения ошибок в программы. Сознавая, что в общем виде эта проблема неразрешима, мы хотим просто поделиться некоторыми неформальными приемами, которые уже приносили пользу другим программистам, работающим на Прологе.
Как и всякое творчество, будь то сочинение музыки, литература или архитектура, программирование располагает множеством способов представления объектов и обработки этих объектов и отношений между ними в конкретной программе. В общем случае для некоторого элемента данных в программе существует несколько возможных способов его представления или обработки. Всякий раз, когда программист решает использовать в данной программе один из них, мы говорим, что программист принял проектное решение.
Новички, впервые столкнувшиеся с задачей выбора проектных решений, часто испытывают трудности. Помочь им может знание возможных вариантов решений, и, кроме того, важно, чтобы руководитель разъяснил им методику программирования в целом, поскольку искусство принятия проектных решений в программировании – это самостоятельная дисциплина. Мы уже пытались затронуть эту проблему в разд. 1.1, где обсуждались различные способы интерпретации утверждений. Эти вопросы связаны с представлением объектов и отношений. В разд. 7.7 мы снова столкнулись с этой проблемой при описании трех различных способов упорядочения списка объектов. Эти вопросы связаны с разными способами обработки объектов и отношений.
13
Тем самым обеспечивает возможность вновь согласовать предикат consult. В противном случае механизм возврата никогда не смог бы миновать repeat, у которого всегда есть альтернативное решение. - Прим. ред.