Заметим, что в то время как предикат clause всегда имеет аргумент, соответствующий телу утверждения, далеко не каждое утверждение действительно имеет тело. Если утверждение не имеет тела, то считается, что оно имеет фиктивное тело true. Мы называли такие утверждения «фактами». В той или иной степени конкретизируя X и Y, можно искать либо все утверждения, соответствующие данному предикату с вполне определенным числом аргументов, либо все утверждения, соответствующие некоторому образцу. Так, например:
присоединить([], X, X).
присоединить([А|В]),С,[А|D]:- присоединить(В,С,D).
?- clause(присоединить(А,В,С),Y).
А=[], В =_23, С=_23, Y = true;
А = [_23|_24], В =_25, С = [_23|_26], Y = присоединить(_24,_25,_26);
нет
Предикат clause очень полезен в том случае, если нам надо создать программы, анализирующие или исполняющие другие программы (см. разд. 7.13).
asserta(X), assertz(X)
Два встроенных предиката asserta и assertz позволяют добавлять новые утверждения в базу данных. Оба предиката действуют в точности одинаковым образом, за тем исключением, что asserta добавляет утверждение в начало базы данных, в то время как assertz добавляет утверждение в ее конец. Это отличие можно легко запомнить, учитывая, что «а» является первой буквой английского алфавита, a «z» его последняя буква. При выполнении целевого утверждения asserta(X), X должно иметь значением нечто, что можно представлять как утверждение; действительно, как и в случае clause, X должно быть достаточно конкретизировано, чтобы можно было установить главный предикат. Необходимо подчеркнуть, что результат добавления в базу данных утверждения не устраняется при выполнении возврата. Следовательно, если мы использовали предикат asserta или assertz для того, чтобы добавить новое утверждение, то это утверждение может быть удалено только в случае, если мы явно укажем это (используя предикат retract).
retract(X)
Встроенный предикат retract позволяет удалять утверждения из базы данных. Этот предикат имеет один аргумент, представляющий терм, с которым должно быть сопоставлено удаляемое утверждение. Указанный терм должен быть достаточно конкретизирован, чтобы можно было определить предикат утверждения (аналогично предикатам asserta, clause и т. д.). При попытке выполнить целевое утверждение retract(X) находится первое утверждение в базе данных, с которым может быть сопоставлен X, и это утверждение удаляется. При попытке вновь выполнить это целевое утверждение Пролог просматривает базу данных, начиная с места удаленного утверждения, в попытке найти другое сопоставимое утверждение. Если такое утверждение находится, то выполняются действия, рассмотренные выше. Если делается новая попытка согласовать целевое утверждение, то продолжается поиск следующего подходящего утверждения. И так далее. Заметим, что если утверждение было удалено, то оно ни при каких условиях не будет восстановлено вновь, даже при попытке вновь выполнить предикат retract при возврате. Если в некоторый момент поиск не дает новых сопоставлений утверждений, то согласование целевого утверждения заканчивается неудачей.
Так как аргумент X сопоставляется с удаляемым утверждением, то имеется возможность получить точное представление об удаляемом утверждении, даже если X исходно означал некоторую структуру, содержащую множество неконкретизированных переменных. Это позволяет использовать предикат retract вместо предиката clause, в ситуации когда найденное утверждение сразу же удаляется. Такая ситуация как раз имеет место в определении предиката генатом (разд. 7.8).
6.5. Создание структур и работа с компонентами структур
Обычно когда мы хотим задать в программе на Прологе операции со структурой определенного вида, то мы делаем это, «упоминая» некоторым образом подобную структуру. Это значит, что если предикат используется для обработки множества структур различного вида, передаваемых ему в качестве аргумента, то обычно мы обеспечиваем отдельное утверждение для каждого класса структур. Хорошим примером такого подхода является программа для символьного дифференцирования, которая будет рассмотрена в разд. 7.1. В этой программе используются отдельные утверждения для функторов +, -, * и так далее. Мы знаем заранее, какие структуры могут появиться, и обеспечиваем утверждения для каждой из них.
В некоторых программах мы не можем предвидеть заранее все возможные структуры. Это имеет место, например, при написании программы «красивой печати», которая могла бы печатать произвольные структуры языка Пролог, размещая их в нескольких строках и используя отступы. (См. разд. 5.1, где представлена такая программа для печати списков.) Так, например, возможно, мы захотели бы напечатать терм
книга(629,автор(бронте, эмили),вх)
следующим образом:
книга
629
автор
бронте
эмили
вх
Важным моментом является то, что мы хотим, чтобы эта программа работала правильно, какую бы структуру мы ей ни задали. Понятно, что одна из возможностей сделать это – обеспечить отдельное утверждение для каждого функтора, какой только можно представить. Но это работа, которую мы никогда не завершим, потому что существует бесконечно много различных функторов! Написать подобную программу можно, используя встроенные предикаты для работы со структурами произвольного вида. Здесь мы опишем некоторые из них – это предикаты functor, arg и '=..'. Мы опишем также предикат name, выполняющий операции над атомами.
functor(T,F,N)
Предикат functor определен таким образом, что functor(T,F,N) означает, что Т – это структура с функтором F, имеющим N аргументов. Этот предикат можно использовать двумя основными способами, В первом случае аргумент Т уже имеет значение. Целевое утверждение считается несогласованным с базой данных, если Т не является ни атомом, ни структурой. Если Т – это атом или структура, то F сопоставляется с функтором этой структуры, а N присваивается значение, равное числу аргументов функтора. Заметим, что в данном контексте считается, что атом – это структура с числом аргументов 0. Ниже приведено несколько примеров целевых утверждений с предикатом functor:
?- functor(f(a,b,g(Z)),F,N).
Z = _23, F = f, N = 3
?- functor(a+b,F,N).
F = +, N = 2
?- functor([a,b,c],F,N).
F =., N = 2
?- functor(apple,F,N).
F = apple, N = 0
?- functor([a,b,c],'.',3).
нет
?- functor([a,b,c],a,Z).
нет
Прежде чем перейти к обсуждению предиката arg, следует рассмотреть второй способ использования предиката functor. В этом случае первый аргумент целевого утверждения functor (Т, F, N) неконкретизирован. В этом случае два других аргумента должны быть конкретизированы, однозначно определяя функтор и число аргументов соответственно. Целевое утверждение такого вида всегда согласуется с базой данных, и в результате значением Т становится структура с указанными функтором и числом аргументов. Таким образом, это некоторый способ создания произвольных структур по заданным функтору структуры и числу ее аргументов. Аргументами такой структуры, созданной с помощью предиката functor, являются неконкретизированные переменные. Следовательно, эта структура будет сопоставима с любой другой структурой, имеющей тот же функтор и одинаковое число аргументов.
Предикат functor используется для создания структуры в основном тогда, когда нам надо получить «копию» некоторой уже существующей структуры с новыми переменными в качестве аргументов. Мы можем ввести для этого предикат копирование, использующий functor как целевое утверждение:
копирование(Старая, Новая):- functor(Cтapaя,F,N), functor(Hoвaя,F,N).
В этом определении подряд используются два целевых утверждения functor. Если целевое утверждение копирование имеет конкретизированный первый аргумент и неконкретизированный второй, то произойдет следующее. Первое целевое утверждение functor будет соответствовать первому способу использования этого предиката (так как первый аргумент этого предиката конкретизирован). Следовательно, F и N конкретизируются, получив в качестве значений функтор и число аргументов этой существующей структуры. Второе целевое утверждение functor соответствует второму способу использования этого предиката. На этот раз первый аргумент неконкретизирован, и информация, задаваемая F и N, используется для создания структуры Новая. Эта структура имеет те же функтор и число аргументов, что и Старая, но ее компонентами являются переменные. Таким образом, возможен следующий диалог: