Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
PROLOG.DOC
Скачиваний:
0
Добавлен:
01.05.2025
Размер:
502.27 Кб
Скачать

10.3. Стиль програмування.

В цьому розділі ми дамо рекомендації для написання елегантних і ефективних програм на Пролозі.

Для виміру ефективності програми традиційно використовують два параметри: пам`ять і час. В Пролозі на покращання цих оцінок значною мірою впливає відміна хвостової рекурсії.

Розглянемо відомий нам предикат member:

member (X, [X|_]).

member (X,[_|Y]): member (X,Y)

Ітеративна операція перевірки або генерації елементів потрібного списку проводиться рекурсивним чином, тому стекова пам`ять (а відповідно і час виконання) повністю залежить від рекурсії. Припустимо, що предикат demopred описується так:

demopred (X,Y): - ... , member (A,B), test (A), ...

При активізації member спочатку система повинна запам`ятати, що після успішного виконання member керування потрібно передати предикату test. Тому, адреса test повинна бути збережена в стеці. Для кожного рекурсивного звертання до member система запам`ятовує адресу, до якої повинен повернутись предикат member після успішного завертання цього предикату. Враховуючи, що між member (X,[_,Y]):- і рекурсивним зверненням member(X,Y) не має точок повернення до попереднього стану, не має необхідності зберігати адресу member в стеці декілька разів. Достатньо запам`ятати, що після успішного завершення предикату member керування повинно бути передане предикату test. Це і буде відміною хвостової рекурсії. Там, де система не може відмінити рекурсію, програміст може зробити це сам, використовуючи наступні правила.

Правило1. Краще використовувати більше змінних, ніж предикатів.

Це правило знаходиться часто в прямому конфлікті з наглядністю програми. В багатьох випадках декларативний стиль Прологу приводить до гіршого коду в порівнянні з процедурними мовами. Наприклад, якщо ви пишете предикат, для перестановки елементів списку, тоді такий фрагмент коду, як:

reverse(X,Y):- reverse1([],X,Y). /*More efficient*/

reverse1(Y, [], X).

reverse1(X1, [U | X2], Y):- reverse1([U|X1],X2,Y).

пред'являє менше вимог до стеку, ніж використання додатково предикату append:

reverse([],[]).

reverse([U | X], Y):- reverse(X,Y1), append(Y1,[U],Y).

append([],Y,Y).

append([U|X],Y,[U|Z]):-append(X,Y,Z).

Правило 2. При впорядкуванні підцілей в правилі, першими розміщуйте підцілі з найбільш зв'язаними змінними.

Наприклад ви пишете предикат для вирішення системи рівнянь

Х + 1 = 4

Х + Y = 5

використовуючи метод "генеруй_ і_перевіряй":

solve(X,Y):- /*кращій варіант*/

num(X), plus(X, 1, 4),

num(Y), plus(X, Y, 5).

Тоді він буде кращим за наступний фрагмент :

solve(X,Y):- /*гірший варіант*/

num(X), num(Y),

plus(X, Y, 5), plus(X, 1, 4).

Предикати num і plus визначались нами раніше.

Правило 3. Коли не існує рішень, пробуйте перевіряти, що виконання видасть повідомлення про "неуспіх" эффективно.

Припустимо, що ми хочемо написати предикат singlepeak, який перевіряє наявність "піку" серед списку цілих чисел. Іншими словами, числа повинні зростати до одного максимуму, а потім спадати. Для такого предикату виклик

singlepeak ([1,2,5,7,11,8,6,4]).

буде успішним, тоді як виклик

singlepeak ([1,2,3,9,6,8,5,4,3]).

видасть повідомлення "неуспіх".

Наступне визначення для singlepeak не враховує Правило 3, тому що наявність в списку однієї "вершини" проводиться тільки у випадку, коли append розбиває список на різноманітні декомпозиції:

singlepeak(X):- append(X1,X2,X), up(X1), down(X2).

up(_).

up([U,V|Y]):- U < V, up([V,Y]).

down([]).

down([U]).

down([U,V|Y]):- U > V, down ([V|Y]).

Можна також запропонувати варіанти, які враховують настанови Правила3:

singlepeak([]).

singlepeak([U,V|Y]):- U < V, singlepeak([U|Y]).

singlepeak([U,V|Y]):- U > V, down([V|Y]).

down([]).

down([U]).

down([U,V|Y]):- U < V, down ([V|Y]).

або ж варіант:

singlepeak([],_).

singlepeak([U,V|W],up):- U < V, singlepeak([V|W],Up).

singlepeak([U,V|W],_):- U > V, singlepeak([V|W],Down).

Правило 4. Механізм уніфікації повинен виконувати як можна більше роботи.

Наприклад, для перевірки на рівність двох списків можна використати наступний фрагмент програми:

equal([],[]).

equal([U|X],[U|Y]):-equal(X,Y).

aле в цьому немає потреби, тому що предикат

equal(Х,Х)

за рахунок механізму уніфікації виконає всю потрібну роботу.

Правило 5. Для повтору краще використовуйте бектрекінг ніж рекурсію.

Бектрекінг зменшує стекові вимоги. Ідея заключається у тому, щоб використовувати конструкцію типу repeat...fail замість рекурсії. Наприклад, для повторного обчислення деякого предикату process(X,Y) можна використати наступну послідовність предикатів:

run:-readln(X),

process(X,Y),

write(Y),

run.

Але комбінація repeat...fail зменшує необхідність кінечного рекурсивного виклику. Визначивши

repeat.

repeat:-repeat.

ми можемо перевизначити run без рекурсії:

run:-repeat,

readln(X),

process(X,Y),

write(Y),

fail.

Тут, fail примушує Пролог виконувати бектрекінг в repeat, який завжди виконується успішно.

Література.

1. И. Братко. Программирование на языке Пролог для искусственного интеллекта. Москва, Мир, 1990.

2. Дж. Малпас. Реляционный язык Пролог и его применение. Москва, Мир, 1990.

82

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]