Изменить стиль страницы

s(*,X,1,X).

s(*,1,X,X).

Эта таблицы упрощений позволяет нам любое выражение вида 1*Х отобразить в X. Посмотрим, как можно воспользоваться этим в программе.

При приведении выражения Е с помощью такой таблицы упрощений, мы вначале должны привести левый аргумент Е, затем привести правый аргумент Е и, наконец, посмотреть, подходит ли этот приведенный результат под случаи, предусмотренные в нашей таблице. Если это так, то мы порождаем новое выражение в соответствии с указаниями таблицы. В качестве «листьев» дерева, представляющего выражение, фигурируют целые числа или атомы, поэтому для приведения листьев к ним самим в граничном условии мы должны использовать встроенный предикат atomic. Как и выше, мы можем использовать ' =..', чтобы разложить выражение Е на функтор и компоненты;

привести(Е,Е):- atomic(E), 1.

привести(Е,F):-Е =.. [Op,L,R],привести(L,Х),привести(R, Y),s(Op,X,Y,F).

Итак, предикат привести отображает выражение Е в выражение F, используя для этого факты, имеющиеся в таблице упрощений s. А что делать, если невозможны никакие упрощения? Чтобы избежать в этом случае неудачного завершения s(Op,X, Y, F), мы должны поместить в конец каждого раздела таблицы упрощений, относящегося к определенному оператору, правило-ловушку. Приведенная ниже таблица упрощений содержит правила для сложения и умножения. Кроме того, в ней выделены правила-ловушки для каждого вида операций.

s(+,X,0,X).

s(+,0,X,X).

s(+,X,Y,X + Y) /* ловушка для + */

s(*,_,0,0).

s(*,0,_,0).

s(*,1,X,X).

s(*,X,1,X).

s(*,X,Y,X*Y). /* ловушка для * */

При наличии правил-ловушек возникает вопрос о выборе способа упрощения некоторых выражений. Например, если нам дано выражение 3+0, мы можем либо использовать первый факт, либо применить правило-ловушку для +. Благодаря способу упорядочения фактов, прежде чем применить правило-ловушку Пролог всегда будет пытаться применить правила для специальных случаев. Поэтому первое решение, полученное предикатом привести, всегда будет являться действительно упрощенным выражением (если оно возможно). Однако альтернативные решения будут иметь не самый простой вид из всех возможных.

Другое упрощение, используемое при выполнении алгебраических преобразований с помощью ЭВМ, известно как свертка констант. В выражении 3*4+a константы 3 и 4 могут быть «свернуты», что дает в результате выражение 12+а. Правила свертки констант могут быть добавлены в соответствующие места приведенной выше таблицы упрощений. Правило для сложения констант выглядит следующим образом:

s(+,X,Y,Z):- integer(X), integer(Y), Z is X+Y.

Соответствующие правила для других арифметических операций имеют аналогичный вид.

В коммутативных операциях, таких как умножение и деление, указанные выше упрощения могут давать различный эффект на выражениях, которые записаны по-разному, но алгебраически эквивалентны. Например, если правило свертки констант задано для умножения, то предикат привести совершенно правильно преобразует 2*3*а в 6*а, но а*2*3 или 2*а*3 будут преобразовываться в самих себя. Чтобы понять, почему это так, подумайте над тем, как выглядят деревья, представляющие эти выражения (см. рис. 7.4).

В первом дереве самое нижнее умножение 2*3 можно свернуть, получив 6, но во втором дереве нет поддеревьев, которые было бы можно свернуть. Однако, используя коммутативность умножения, можно добавить к таблице следующее правило, которое позволит справиться с данным конкретным случаем:

s(*,X*Y,W,X*Z):- integer(Y), integer(W), Z is Y*W.

Более общая алгебраическая система может быть построена путем простого добавления дополнительных s-утверждений вместо увеличения объема программы для привести.

Программирование на языке пролог pic_28.jpg

Рис. 7.4

7.13. Применение предикатов clause и retract

В этой книге мы неоднократно сталкивались с применением встроенных предикатов. На самом деле многие из них можно определить на Прологе, используя более простые встроенные предикаты. В этом разделе мы рассмотрим несколько таких определений. Они могут найти практическое применение у тех программистов, которые используют неполную в каких-либо отношениях Пролог-систему, однако в любом случае они интересны, как примеры программирования на Прологе. Может быть, они наведут вас на мысль о разработке несколько отличающихся версий этих предикатов для своего собственного применения.

Мы можем определить с помощью предиката clause некоторую версию процедуры listing. Определим предикат распеч1 такой, что при согласовании цели распеч1(Х) с базой данных из последней будут выводиться на печать утверждения, заголовки которых совпадают с X. Поскольку определение распеч1 включает использование предиката clause, у которого X задан как первый аргумент, то мы вынуждены поставить условие, что переменная X конкретизирована таким образом, что главный функтор утверждения известен. Рассмотрим определение распеч1:

распеч1(Х):-clause(Х,Y),выв_утвержд(Х,Y),write('.'),nl,fail.

распеч1(Х).

выв_утвержд(Х,true):-!, write(X).

выв_утвержд(Х,Y):- write((X:- Y)).

При попытке согласовать с базой данных цель распеч1(Х) первое утверждение осуществляет поиск в базе данных такого утверждения, у которого заголовок совпадает с X. Если такое утверждение найдено, то оно выводится на печать и затем с помощью предиката fail инициируется механизм возврата. Возвратный ход опять приводит нас к предикату clause, который находит другое такое же утверждение, если оно имеется, и т. д. Когда таких утверждений больше нет, цель clause больше не удается согласовать с базой данных. В этом случае будет выбрано второе утверждение определения предиката распеч1, и потому цель будет согласована с базой данных. «Побочным эффектом» этих действий является печать соответствующих утверждений. Определение предиката выв_утвержд задает способ печати найденных утверждений. Выделяется специальный случай, когда тело утверждения есть true. В этом случае на печать выводится только заголовок утверждения. Иначе на печать выводится заголовок и тело утверждения, соединенные функтором ':-'. Отметим, что использование «отсечения» здесь имеет целью указать, что в случае, когда тело есть true, можно применять только первое правило. Поскольку данный пример построен на использовании механизма возврата, то задание отсечения здесь существенно.

Встроенный предикат clause можно также применить при написании Пролог-интерпретатора на самом Прологе. Это означает, что мы можем определить действия, которые представляют собой выполнение Пролог-программы, причем исполнителем этих действий также является Пролог-программа. Ниже приводится определение предиката интерпрет такого, что цель интерпрет(Х) согласуется в том и только в том случае, когда X, рассматриваемая как цель, согласуется с базой данных.

Предикат интерпрет напоминает встроенный предикат call, но является более ограниченным.

интерпрет(true):-!.

интерпрет((Gl,G2)):-!, интерпрет(G1), интерпрет(G2).

интерпрет(Цель):-clause(Цель,ЕщеЦели), интерпрет(ЕщеЦели).

Первые два утверждения рассчитаны на специальные случаи, когда цель есть true и когда цель представляет собой конъюнкцию целей. Последнее утверждение рассчитано на случай простой цели. Данная процедура находит утверждение, заголовок которого совпадает с заданной целью, и затем интерпретирует цели, входящие в тело этого утверждения. Заметим, что приведенное определение не рассчитано на программы, где используются встроенные предикаты, поскольку у таких предикатов нет определяющих их в обычном смысле утверждений.